RetromodСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачатьСкачать
ВЕРСИИ (3055)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 26.3)
1.4.0-snapshot.2+26.3-neoforge19 окт. 2026 г.131Minecraft: 26.3neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 26.2)
1.4.0-snapshot.2+26.2-neoforge19 окт. 2026 г.30Minecraft: 26.2neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 26.1.2)
1.4.0-snapshot.2+26.1.2-neoforge19 окт. 2026 г.35Minecraft: 26.1.2neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 26.1.1)
1.4.0-snapshot.2+26.1.1-neoforge19 окт. 2026 г.5Minecraft: 26.1.1neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 26.1)
1.4.0-snapshot.2+26.1-neoforge19 окт. 2026 г.16Minecraft: 26.1neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.21.11)
1.4.0-snapshot.2+1.21.11-nf19 окт. 2026 г.26Minecraft: 1.21.11neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.21.10)
1.4.0-snapshot.2+1.21.10-nf19 окт. 2026 г.8Minecraft: 1.21.10neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.21.9)
1.4.0-snapshot.2+1.21.9-neoforge19 окт. 2026 г.8Minecraft: 1.21.9neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.21.8)
1.4.0-snapshot.2+1.21.8-neoforge19 окт. 2026 г.13Minecraft: 1.21.8neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.21.7)
1.4.0-snapshot.2+1.21.7-neoforge19 окт. 2026 г.8Minecraft: 1.21.7neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.21.6)
1.4.0-snapshot.2+1.21.6-neoforge19 окт. 2026 г.9Minecraft: 1.21.6neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.21.5)
1.4.0-snapshot.2+1.21.5-neoforge19 окт. 2026 г.9Minecraft: 1.21.5neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.21.4)
1.4.0-snapshot.2+1.21.4-neoforge19 окт. 2026 г.3Minecraft: 1.21.4neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.21.3)
1.4.0-snapshot.2+1.21.3-neoforge19 окт. 2026 г.1Minecraft: 1.21.3neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.21.2)
1.4.0-snapshot.2+1.21.2-neoforge19 окт. 2026 г.4Minecraft: 1.21.2neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.21.1)
1.4.0-snapshot.2+1.21.1-neoforge19 окт. 2026 г.216Minecraft: 1.21.1neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.21)
1.4.0-snapshot.2+1.21-neoforge19 окт. 2026 г.0Minecraft: 1.21neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.20.6)
1.4.0-snapshot.2+1.20.6-neoforge19 окт. 2026 г.6Minecraft: 1.20.6neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.20.5)
1.4.0-snapshot.2+1.20.5-neoforge19 окт. 2026 г.0Minecraft: 1.20.5neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.20.4)
1.4.0-snapshot.2+1.20.4-neoforge19 окт. 2026 г.0Minecraft: 1.20.4neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.20.3)
1.4.0-snapshot.2+1.20.3-neoforge19 окт. 2026 г.1Minecraft: 1.20.3neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.20.2)
1.4.0-snapshot.2+1.20.2-neoforge19 окт. 2026 г.5Minecraft: 1.20.2neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (NeoForge 1.20.1)
1.4.0-snapshot.2+1.20.1-neoforge19 окт. 2026 г.8Minecraft: 1.20.1neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 26.3)
1.4.0-snapshot.2+26.3-forge19 окт. 2026 г.58Minecraft: 26.3forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 26.2)
1.4.0-snapshot.2+26.2-forge19 окт. 2026 г.14Minecraft: 26.2forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 26.1.2)
1.4.0-snapshot.2+26.1.2-forge19 окт. 2026 г.3Minecraft: 26.1.2forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 26.1.1)
1.4.0-snapshot.2+26.1.1-forge19 окт. 2026 г.0Minecraft: 26.1.1forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 26.1)
1.4.0-snapshot.2+26.1-forge19 окт. 2026 г.1Minecraft: 26.1forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.21.11)
1.4.0-snapshot.2+1.21.11-forge19 окт. 2026 г.2Minecraft: 1.21.11forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.21.10)
1.4.0-snapshot.2+1.21.10-forge19 окт. 2026 г.12Minecraft: 1.21.10forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.21.9)
1.4.0-snapshot.2+1.21.9-forge19 окт. 2026 г.2Minecraft: 1.21.9forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.21.8)
1.4.0-snapshot.2+1.21.8-forge19 окт. 2026 г.8Minecraft: 1.21.8forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.21.7)
1.4.0-snapshot.2+1.21.7-forge19 окт. 2026 г.0Minecraft: 1.21.7forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.21.6)
1.4.0-snapshot.2+1.21.6-forge19 окт. 2026 г.13Minecraft: 1.21.6forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.21.5)
1.4.0-snapshot.2+1.21.5-forge19 окт. 2026 г.0Minecraft: 1.21.5forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.21.4)
1.4.0-snapshot.2+1.21.4-forge19 окт. 2026 г.9Minecraft: 1.21.4forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.21.3)
1.4.0-snapshot.2+1.21.3-forge19 окт. 2026 г.2Minecraft: 1.21.3forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.21.2)
1.4.0-snapshot.2+1.21.2-forge19 окт. 2026 г.1Minecraft: 1.21.2forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.21.1)
1.4.0-snapshot.2+1.21.1-forge19 окт. 2026 г.24Minecraft: 1.21.1forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.21)
1.4.0-snapshot.2+1.21-forge19 окт. 2026 г.0Minecraft: 1.21forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.20.6)
1.4.0-snapshot.2+1.20.6-forge19 окт. 2026 г.7Minecraft: 1.20.6forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.20.5)
1.4.0-snapshot.2+1.20.5-forge19 окт. 2026 г.3Minecraft: 1.20.5forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.20.4)
1.4.0-snapshot.2+1.20.4-forge19 окт. 2026 г.0Minecraft: 1.20.4forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.20.3)
1.4.0-snapshot.2+1.20.3-forge19 окт. 2026 г.1Minecraft: 1.20.3forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.20.2)
1.4.0-snapshot.2+1.20.2-forge19 окт. 2026 г.1Minecraft: 1.20.2forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.20.1)
1.4.0-snapshot.2+1.20.1-forge19 окт. 2026 г.136Minecraft: 1.20.1forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Forge 1.20)
1.4.0-snapshot.2+1.20-forge19 окт. 2026 г.0Minecraft: 1.20forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 26.3)
1.4.0-snapshot.2+26.3-fabric19 окт. 2026 г.572Minecraft: 26.3fabric, quilt
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 26.2)
1.4.0-snapshot.2+26.2-fabric19 окт. 2026 г.184Minecraft: 26.2fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 26.1.2)
1.4.0-snapshot.2+26.1.2-fabric19 окт. 2026 г.48Minecraft: 26.1.2fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 26.1.1)
1.4.0-snapshot.2+26.1.1-fabric19 окт. 2026 г.13Minecraft: 26.1.1fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 26.1)
1.4.0-snapshot.2+26.1-fabric19 окт. 2026 г.5Minecraft: 26.1fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.21.11)
1.4.0-snapshot.2+1.21.11-fabric19 окт. 2026 г.150Minecraft: 1.21.11fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.21.10)
1.4.0-snapshot.2+1.21.10-fabric19 окт. 2026 г.7Minecraft: 1.21.10fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.21.9)
1.4.0-snapshot.2+1.21.9-fabric19 окт. 2026 г.3Minecraft: 1.21.9fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.21.8)
1.4.0-snapshot.2+1.21.8-fabric19 окт. 2026 г.4Minecraft: 1.21.8fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.21.7)
1.4.0-snapshot.2+1.21.7-fabric19 окт. 2026 г.3Minecraft: 1.21.7fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.21.6)
1.4.0-snapshot.2+1.21.6-fabric19 окт. 2026 г.6Minecraft: 1.21.6fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.21.5)
1.4.0-snapshot.2+1.21.5-fabric19 окт. 2026 г.2Minecraft: 1.21.5fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.21.4)
1.4.0-snapshot.2+1.21.4-fabric19 окт. 2026 г.12Minecraft: 1.21.4fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.21.3)
1.4.0-snapshot.2+1.21.3-fabric19 окт. 2026 г.2Minecraft: 1.21.3fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.21.2)
1.4.0-snapshot.2+1.21.2-fabric19 окт. 2026 г.1Minecraft: 1.21.2fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.21.1)
1.4.0-snapshot.2+1.21.1-fabric19 окт. 2026 г.75Minecraft: 1.21.1fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.21)
1.4.0-snapshot.2+1.21-fabric19 окт. 2026 г.7Minecraft: 1.21fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.20.6)
1.4.0-snapshot.2+1.20.6-fabric19 окт. 2026 г.5Minecraft: 1.20.6fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.20.5)
1.4.0-snapshot.2+1.20.5-fabric19 окт. 2026 г.11Minecraft: 1.20.5fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.20.4)
1.4.0-snapshot.2+1.20.4-fabric19 окт. 2026 г.7Minecraft: 1.20.4fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.20.3)
1.4.0-snapshot.2+1.20.3-fabric19 окт. 2026 г.2Minecraft: 1.20.3fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.20.2)
1.4.0-snapshot.2+1.20.2-fabric19 окт. 2026 г.2Minecraft: 1.20.2fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.20.1)
1.4.0-snapshot.2+1.20.1-fabric19 окт. 2026 г.19Minecraft: 1.20.1fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.2 (Fabric 1.20)
1.4.0-snapshot.2+1.20-fabric19 окт. 2026 г.4Minecraft: 1.20fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.2](https://bownlux.dev/retromod/changelog.html#snapshot-2-october-18-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 26.3)
1.4.0-snapshot.1+26.3-neoforge13 окт. 2026 г.94Minecraft: 26.3neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 26.2)
1.4.0-snapshot.1+26.2-neoforge13 окт. 2026 г.34Minecraft: 26.2neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 26.1.2)
1.4.0-snapshot.1+26.1.2-neoforge13 окт. 2026 г.44Minecraft: 26.1.2neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 26.1.1)
1.4.0-snapshot.1+26.1.1-neoforge13 окт. 2026 г.9Minecraft: 26.1.1neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 26.1)
1.4.0-snapshot.1+26.1-neoforge13 окт. 2026 г.14Minecraft: 26.1neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.21.11)
1.4.0-snapshot.1+1.21.11-nf13 окт. 2026 г.18Minecraft: 1.21.11neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.21.10)
1.4.0-snapshot.1+1.21.10-nf13 окт. 2026 г.14Minecraft: 1.21.10neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.21.9)
1.4.0-snapshot.1+1.21.9-neoforge13 окт. 2026 г.10Minecraft: 1.21.9neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.21.8)
1.4.0-snapshot.1+1.21.8-neoforge13 окт. 2026 г.14Minecraft: 1.21.8neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.21.7)
1.4.0-snapshot.1+1.21.7-neoforge13 окт. 2026 г.10Minecraft: 1.21.7neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.21.6)
1.4.0-snapshot.1+1.21.6-neoforge13 окт. 2026 г.8Minecraft: 1.21.6neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.21.5)
1.4.0-snapshot.1+1.21.5-neoforge13 окт. 2026 г.10Minecraft: 1.21.5neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.21.4)
1.4.0-snapshot.1+1.21.4-neoforge13 окт. 2026 г.2Minecraft: 1.21.4neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.21.3)
1.4.0-snapshot.1+1.21.3-neoforge13 окт. 2026 г.1Minecraft: 1.21.3neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.21.2)
1.4.0-snapshot.1+1.21.2-neoforge13 окт. 2026 г.4Minecraft: 1.21.2neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.21.1)
1.4.0-snapshot.1+1.21.1-neoforge13 окт. 2026 г.187Minecraft: 1.21.1neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.21)
1.4.0-snapshot.1+1.21-neoforge13 окт. 2026 г.0Minecraft: 1.21neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.20.6)
1.4.0-snapshot.1+1.20.6-neoforge13 окт. 2026 г.2Minecraft: 1.20.6neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.20.5)
1.4.0-snapshot.1+1.20.5-neoforge13 окт. 2026 г.0Minecraft: 1.20.5neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.20.4)
1.4.0-snapshot.1+1.20.4-neoforge13 окт. 2026 г.2Minecraft: 1.20.4neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.20.3)
1.4.0-snapshot.1+1.20.3-neoforge13 окт. 2026 г.0Minecraft: 1.20.3neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.20.2)
1.4.0-snapshot.1+1.20.2-neoforge13 окт. 2026 г.0Minecraft: 1.20.2neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (NeoForge 1.20.1)
1.4.0-snapshot.1+1.20.1-neoforge13 окт. 2026 г.20Minecraft: 1.20.1neoforge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 26.3)
1.4.0-snapshot.1+26.3-forge13 окт. 2026 г.44Minecraft: 26.3forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 26.2)
1.4.0-snapshot.1+26.2-forge13 окт. 2026 г.8Minecraft: 26.2forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 26.1.2)
1.4.0-snapshot.1+26.1.2-forge13 окт. 2026 г.15Minecraft: 26.1.2forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 26.1.1)
1.4.0-snapshot.1+26.1.1-forge13 окт. 2026 г.10Minecraft: 26.1.1forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 26.1)
1.4.0-snapshot.1+26.1-forge13 окт. 2026 г.10Minecraft: 26.1forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.21.11)
1.4.0-snapshot.1+1.21.11-forge13 окт. 2026 г.12Minecraft: 1.21.11forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.21.10)
1.4.0-snapshot.1+1.21.10-forge13 окт. 2026 г.10Minecraft: 1.21.10forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.21.9)
1.4.0-snapshot.1+1.21.9-forge13 окт. 2026 г.10Minecraft: 1.21.9forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.21.8)
1.4.0-snapshot.1+1.21.8-forge13 окт. 2026 г.9Minecraft: 1.21.8forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.21.7)
1.4.0-snapshot.1+1.21.7-forge13 окт. 2026 г.11Minecraft: 1.21.7forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.21.6)
1.4.0-snapshot.1+1.21.6-forge13 окт. 2026 г.0Minecraft: 1.21.6forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.21.5)
1.4.0-snapshot.1+1.21.5-forge13 окт. 2026 г.6Minecraft: 1.21.5forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.21.4)
1.4.0-snapshot.1+1.21.4-forge13 окт. 2026 г.2Minecraft: 1.21.4forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.21.3)
1.4.0-snapshot.1+1.21.3-forge13 окт. 2026 г.1Minecraft: 1.21.3forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.21.2)
1.4.0-snapshot.1+1.21.2-forge13 окт. 2026 г.0Minecraft: 1.21.2forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.21.1)
1.4.0-snapshot.1+1.21.1-forge13 окт. 2026 г.18Minecraft: 1.21.1forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.21)
1.4.0-snapshot.1+1.21-forge13 окт. 2026 г.0Minecraft: 1.21forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.20.6)
1.4.0-snapshot.1+1.20.6-forge13 окт. 2026 г.4Minecraft: 1.20.6forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.20.5)
1.4.0-snapshot.1+1.20.5-forge13 окт. 2026 г.0Minecraft: 1.20.5forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.20.4)
1.4.0-snapshot.1+1.20.4-forge13 окт. 2026 г.2Minecraft: 1.20.4forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.20.3)
1.4.0-snapshot.1+1.20.3-forge13 окт. 2026 г.0Minecraft: 1.20.3forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.20.2)
1.4.0-snapshot.1+1.20.2-forge13 окт. 2026 г.0Minecraft: 1.20.2forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.20.1)
1.4.0-snapshot.1+1.20.1-forge13 окт. 2026 г.93Minecraft: 1.20.1forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Forge 1.20)
1.4.0-snapshot.1+1.20-forge13 окт. 2026 г.0Minecraft: 1.20forge
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 26.3)
1.4.0-snapshot.1+26.3-fabric13 окт. 2026 г.441Minecraft: 26.3fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 26.2)
1.4.0-snapshot.1+26.2-fabric13 окт. 2026 г.209Minecraft: 26.2fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 26.1.2)
1.4.0-snapshot.1+26.1.2-fabric13 окт. 2026 г.44Minecraft: 26.1.2fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 26.1.1)
1.4.0-snapshot.1+26.1.1-fabric13 окт. 2026 г.8Minecraft: 26.1.1fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 26.1)
1.4.0-snapshot.1+26.1-fabric13 окт. 2026 г.22Minecraft: 26.1fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.21.11)
1.4.0-snapshot.1+1.21.11-fabric13 окт. 2026 г.104Minecraft: 1.21.11fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.21.10)
1.4.0-snapshot.1+1.21.10-fabric13 окт. 2026 г.14Minecraft: 1.21.10fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.21.9)
1.4.0-snapshot.1+1.21.9-fabric13 окт. 2026 г.0Minecraft: 1.21.9fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.21.8)
1.4.0-snapshot.1+1.21.8-fabric13 окт. 2026 г.12Minecraft: 1.21.8fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.21.7)
1.4.0-snapshot.1+1.21.7-fabric13 окт. 2026 г.10Minecraft: 1.21.7fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.21.6)
1.4.0-snapshot.1+1.21.6-fabric13 окт. 2026 г.7Minecraft: 1.21.6fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.21.5)
1.4.0-snapshot.1+1.21.5-fabric13 окт. 2026 г.14Minecraft: 1.21.5fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.21.4)
1.4.0-snapshot.1+1.21.4-fabric13 окт. 2026 г.4Minecraft: 1.21.4fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.21.3)
1.4.0-snapshot.1+1.21.3-fabric13 окт. 2026 г.0Minecraft: 1.21.3fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.21.2)
1.4.0-snapshot.1+1.21.2-fabric13 окт. 2026 г.4Minecraft: 1.21.2fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.21.1)
1.4.0-snapshot.1+1.21.1-fabric13 окт. 2026 г.44Minecraft: 1.21.1fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.21)
1.4.0-snapshot.1+1.21-fabric13 окт. 2026 г.4Minecraft: 1.21fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.20.6)
1.4.0-snapshot.1+1.20.6-fabric13 окт. 2026 г.4Minecraft: 1.20.6fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.20.5)
1.4.0-snapshot.1+1.20.5-fabric13 окт. 2026 г.2Minecraft: 1.20.5fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.20.4)
1.4.0-snapshot.1+1.20.4-fabric13 окт. 2026 г.8Minecraft: 1.20.4fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.20.3)
1.4.0-snapshot.1+1.20.3-fabric13 окт. 2026 г.2Minecraft: 1.20.3fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.20.2)
1.4.0-snapshot.1+1.20.2-fabric13 окт. 2026 г.2Minecraft: 1.20.2fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.20.1)
1.4.0-snapshot.1+1.20.1-fabric13 окт. 2026 г.16Minecraft: 1.20.1fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
beta
Retromod 1.4.0-snapshot.1 (Fabric 1.20)
1.4.0-snapshot.1+1.20-fabric13 окт. 2026 г.2Minecraft: 1.20fabric
Что нового
Changelog: [Retromod 1.4.0-snapshot.1](https://bownlux.dev/retromod/changelog.html#snapshot-1-october-10-2026)
release
Retromod 1.3.4 (NeoForge 26.3)
1.3.4+26.3-neoforge10 окт. 2026 г.158Minecraft: 26.3neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 26.2)
1.3.4+26.2-neoforge10 окт. 2026 г.72Minecraft: 26.2neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 26.1.2)
1.3.4+26.1.2-neoforge10 окт. 2026 г.48Minecraft: 26.1.2neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 26.1.1)
1.3.4+26.1.1-neoforge10 окт. 2026 г.12Minecraft: 26.1.1neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 26.1)
1.3.4+26.1-neoforge10 окт. 2026 г.14Minecraft: 26.1neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.21.11)
1.3.4+1.21.11-neoforge10 окт. 2026 г.25Minecraft: 1.21.11neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.21.10)
1.3.4+1.21.10-neoforge10 окт. 2026 г.12Minecraft: 1.21.10neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.21.9)
1.3.4+1.21.9-neoforge10 окт. 2026 г.8Minecraft: 1.21.9neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.21.8)
1.3.4+1.21.8-neoforge10 окт. 2026 г.12Minecraft: 1.21.8neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.21.7)
1.3.4+1.21.7-neoforge10 окт. 2026 г.0Minecraft: 1.21.7neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.21.6)
1.3.4+1.21.6-neoforge10 окт. 2026 г.6Minecraft: 1.21.6neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.21.5)
1.3.4+1.21.5-neoforge10 окт. 2026 г.8Minecraft: 1.21.5neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.21.4)
1.3.4+1.21.4-neoforge10 окт. 2026 г.24Minecraft: 1.21.4neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.21.3)
1.3.4+1.21.3-neoforge10 окт. 2026 г.8Minecraft: 1.21.3neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.21.2)
1.3.4+1.21.2-neoforge10 окт. 2026 г.2Minecraft: 1.21.2neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.21.1)
1.3.4+1.21.1-neoforge10 окт. 2026 г.200Minecraft: 1.21.1neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.21)
1.3.4+1.21-neoforge10 окт. 2026 г.0Minecraft: 1.21neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.20.6)
1.3.4+1.20.6-neoforge10 окт. 2026 г.2Minecraft: 1.20.6neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.20.5)
1.3.4+1.20.5-neoforge10 окт. 2026 г.0Minecraft: 1.20.5neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.20.4)
1.3.4+1.20.4-neoforge10 окт. 2026 г.2Minecraft: 1.20.4neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.20.3)
1.3.4+1.20.3-neoforge10 окт. 2026 г.0Minecraft: 1.20.3neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.20.2)
1.3.4+1.20.2-neoforge10 окт. 2026 г.0Minecraft: 1.20.2neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (NeoForge 1.20.1)
1.3.4+1.20.1-neoforge10 окт. 2026 г.18Minecraft: 1.20.1neoforge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 26.3)
1.3.4+26.3-forge10 окт. 2026 г.139Minecraft: 26.3forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 26.2)
1.3.4+26.2-forge10 окт. 2026 г.26Minecraft: 26.2forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 26.1.2)
1.3.4+26.1.2-forge10 окт. 2026 г.14Minecraft: 26.1.2forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 26.1.1)
1.3.4+26.1.1-forge10 окт. 2026 г.8Minecraft: 26.1.1forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 26.1)
1.3.4+26.1-forge10 окт. 2026 г.12Minecraft: 26.1forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.21.11)
1.3.4+1.21.11-forge10 окт. 2026 г.26Minecraft: 1.21.11forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.21.10)
1.3.4+1.21.10-forge10 окт. 2026 г.0Minecraft: 1.21.10forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.21.9)
1.3.4+1.21.9-forge10 окт. 2026 г.2Minecraft: 1.21.9forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.21.8)
1.3.4+1.21.8-forge10 окт. 2026 г.14Minecraft: 1.21.8forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.21.7)
1.3.4+1.21.7-forge10 окт. 2026 г.0Minecraft: 1.21.7forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.21.6)
1.3.4+1.21.6-forge10 окт. 2026 г.0Minecraft: 1.21.6forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.21.5)
1.3.4+1.21.5-forge10 окт. 2026 г.6Minecraft: 1.21.5forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.21.4)
1.3.4+1.21.4-forge10 окт. 2026 г.2Minecraft: 1.21.4forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.21.3)
1.3.4+1.21.3-forge10 окт. 2026 г.0Minecraft: 1.21.3forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.21.2)
1.3.4+1.21.2-forge10 окт. 2026 г.0Minecraft: 1.21.2forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.21.1)
1.3.4+1.21.1-forge10 окт. 2026 г.42Minecraft: 1.21.1forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.21)
1.3.4+1.21-forge10 окт. 2026 г.0Minecraft: 1.21forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.20.6)
1.3.4+1.20.6-forge10 окт. 2026 г.2Minecraft: 1.20.6forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.20.5)
1.3.4+1.20.5-forge10 окт. 2026 г.2Minecraft: 1.20.5forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.20.4)
1.3.4+1.20.4-forge10 окт. 2026 г.4Minecraft: 1.20.4forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.20.3)
1.3.4+1.20.3-forge10 окт. 2026 г.0Minecraft: 1.20.3forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.20.2)
1.3.4+1.20.2-forge10 окт. 2026 г.4Minecraft: 1.20.2forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.20.1)
1.3.4+1.20.1-forge10 окт. 2026 г.178Minecraft: 1.20.1forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Forge 1.20)
1.3.4+1.20-forge10 окт. 2026 г.2Minecraft: 1.20forge
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 26.3)
1.3.4+26.3-fabric10 окт. 2026 г.1,2 тыс.Minecraft: 26.3fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 26.2)
1.3.4+26.2-fabric10 окт. 2026 г.439Minecraft: 26.2fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 26.1.2)
1.3.4+26.1.2-fabric10 окт. 2026 г.102Minecraft: 26.1.2fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 26.1.1)
1.3.4+26.1.1-fabric10 окт. 2026 г.15Minecraft: 26.1.1fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 26.1)
1.3.4+26.1-fabric10 окт. 2026 г.21Minecraft: 26.1fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.21.11)
1.3.4+1.21.11-fabric10 окт. 2026 г.235Minecraft: 1.21.11fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.21.10)
1.3.4+1.21.10-fabric10 окт. 2026 г.20Minecraft: 1.21.10fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.21.9)
1.3.4+1.21.9-fabric10 окт. 2026 г.6Minecraft: 1.21.9fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.21.8)
1.3.4+1.21.8-fabric10 окт. 2026 г.17Minecraft: 1.21.8fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.21.7)
1.3.4+1.21.7-fabric10 окт. 2026 г.6Minecraft: 1.21.7fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.21.6)
1.3.4+1.21.6-fabric10 окт. 2026 г.4Minecraft: 1.21.6fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.21.5)
1.3.4+1.21.5-fabric10 окт. 2026 г.10Minecraft: 1.21.5fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.21.4)
1.3.4+1.21.4-fabric10 окт. 2026 г.36Minecraft: 1.21.4fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.21.3)
1.3.4+1.21.3-fabric10 окт. 2026 г.0Minecraft: 1.21.3fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.21.2)
1.3.4+1.21.2-fabric10 окт. 2026 г.2Minecraft: 1.21.2fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.21.1)
1.3.4+1.21.1-fabric10 окт. 2026 г.72Minecraft: 1.21.1fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.21)
1.3.4+1.21-fabric10 окт. 2026 г.4Minecraft: 1.21fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.20.6)
1.3.4+1.20.6-fabric10 окт. 2026 г.6Minecraft: 1.20.6fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.20.5)
1.3.4+1.20.5-fabric10 окт. 2026 г.1Minecraft: 1.20.5fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.20.4)
1.3.4+1.20.4-fabric10 окт. 2026 г.12Minecraft: 1.20.4fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.20.3)
1.3.4+1.20.3-fabric10 окт. 2026 г.5Minecraft: 1.20.3fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.20.2)
1.3.4+1.20.2-fabric10 окт. 2026 г.5Minecraft: 1.20.2fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.20.1)
1.3.4+1.20.1-fabric10 окт. 2026 г.40Minecraft: 1.20.1fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.4 (Fabric 1.20)
1.3.4+1.20-fabric10 окт. 2026 г.16Minecraft: 1.20fabric
Что нового
Changelog: [Retromod 1.3.4](https://bownlux.dev/retromod/changelog.html#134-october-9-2026)
release
Retromod 1.3.3 (NeoForge 26.3)
1.3.3+26.3-neoforge8 окт. 2026 г.57Minecraft: 26.3neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 26.2)
1.3.3+26.2-neoforge8 окт. 2026 г.33Minecraft: 26.2neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 26.1.2)
1.3.3+26.1.2-neoforge8 окт. 2026 г.23Minecraft: 26.1.2neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 26.1.1)
1.3.3+26.1.1-neoforge8 окт. 2026 г.4Minecraft: 26.1.1neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 26.1)
1.3.3+26.1-neoforge8 окт. 2026 г.5Minecraft: 26.1neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.21.11)
1.3.3+1.21.11-neoforge8 окт. 2026 г.4Minecraft: 1.21.11neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.21.10)
1.3.3+1.21.10-neoforge8 окт. 2026 г.0Minecraft: 1.21.10neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.21.9)
1.3.3+1.21.9-neoforge8 окт. 2026 г.8Minecraft: 1.21.9neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.21.8)
1.3.3+1.21.8-neoforge8 окт. 2026 г.0Minecraft: 1.21.8neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.21.7)
1.3.3+1.21.7-neoforge8 окт. 2026 г.0Minecraft: 1.21.7neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.21.6)
1.3.3+1.21.6-neoforge8 окт. 2026 г.0Minecraft: 1.21.6neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.21.5)
1.3.3+1.21.5-neoforge8 окт. 2026 г.2Minecraft: 1.21.5neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.21.4)
1.3.3+1.21.4-neoforge8 окт. 2026 г.10Minecraft: 1.21.4neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.21.3)
1.3.3+1.21.3-neoforge8 окт. 2026 г.10Minecraft: 1.21.3neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.21.2)
1.3.3+1.21.2-neoforge8 окт. 2026 г.4Minecraft: 1.21.2neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.21.1)
1.3.3+1.21.1-neoforge8 окт. 2026 г.86Minecraft: 1.21.1neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.21)
1.3.3+1.21-neoforge8 окт. 2026 г.0Minecraft: 1.21neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.20.6)
1.3.3+1.20.6-neoforge8 окт. 2026 г.1Minecraft: 1.20.6neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.20.5)
1.3.3+1.20.5-neoforge8 окт. 2026 г.0Minecraft: 1.20.5neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.20.4)
1.3.3+1.20.4-neoforge8 окт. 2026 г.0Minecraft: 1.20.4neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.20.3)
1.3.3+1.20.3-neoforge8 окт. 2026 г.0Minecraft: 1.20.3neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.20.2)
1.3.3+1.20.2-neoforge8 окт. 2026 г.0Minecraft: 1.20.2neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (NeoForge 1.20.1)
1.3.3+1.20.1-neoforge8 окт. 2026 г.4Minecraft: 1.20.1neoforge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 26.3)
1.3.3+26.3-forge8 окт. 2026 г.20Minecraft: 26.3forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 26.2)
1.3.3+26.2-forge8 окт. 2026 г.8Minecraft: 26.2forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 26.1.2)
1.3.3+26.1.2-forge8 окт. 2026 г.4Minecraft: 26.1.2forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 26.1.1)
1.3.3+26.1.1-forge8 окт. 2026 г.2Minecraft: 26.1.1forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 26.1)
1.3.3+26.1-forge8 окт. 2026 г.0Minecraft: 26.1forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.21.11)
1.3.3+1.21.11-forge8 окт. 2026 г.2Minecraft: 1.21.11forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.21.10)
1.3.3+1.21.10-forge8 окт. 2026 г.2Minecraft: 1.21.10forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.21.9)
1.3.3+1.21.9-forge8 окт. 2026 г.2Minecraft: 1.21.9forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.21.8)
1.3.3+1.21.8-forge8 окт. 2026 г.0Minecraft: 1.21.8forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.21.7)
1.3.3+1.21.7-forge8 окт. 2026 г.0Minecraft: 1.21.7forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.21.6)
1.3.3+1.21.6-forge8 окт. 2026 г.0Minecraft: 1.21.6forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.21.5)
1.3.3+1.21.5-forge8 окт. 2026 г.0Minecraft: 1.21.5forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.21.4)
1.3.3+1.21.4-forge8 окт. 2026 г.0Minecraft: 1.21.4forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.21.3)
1.3.3+1.21.3-forge8 окт. 2026 г.0Minecraft: 1.21.3forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.21.2)
1.3.3+1.21.2-forge8 окт. 2026 г.0Minecraft: 1.21.2forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.21.1)
1.3.3+1.21.1-forge8 окт. 2026 г.16Minecraft: 1.21.1forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.21)
1.3.3+1.21-forge8 окт. 2026 г.0Minecraft: 1.21forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.20.6)
1.3.3+1.20.6-forge8 окт. 2026 г.0Minecraft: 1.20.6forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.20.5)
1.3.3+1.20.5-forge8 окт. 2026 г.3Minecraft: 1.20.5forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.20.4)
1.3.3+1.20.4-forge8 окт. 2026 г.0Minecraft: 1.20.4forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.20.3)
1.3.3+1.20.3-forge8 окт. 2026 г.0Minecraft: 1.20.3forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.20.2)
1.3.3+1.20.2-forge8 окт. 2026 г.0Minecraft: 1.20.2forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.20.1)
1.3.3+1.20.1-forge8 окт. 2026 г.30Minecraft: 1.20.1forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Forge 1.20)
1.3.3+1.20-forge8 окт. 2026 г.2Minecraft: 1.20forge
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 26.3)
1.3.3+26.3-fabric8 окт. 2026 г.268Minecraft: 26.3fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 26.2)
1.3.3+26.2-fabric8 окт. 2026 г.183Minecraft: 26.2fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 26.1.2)
1.3.3+26.1.2-fabric8 окт. 2026 г.24Minecraft: 26.1.2fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 26.1.1)
1.3.3+26.1.1-fabric8 окт. 2026 г.3Minecraft: 26.1.1fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 26.1)
1.3.3+26.1-fabric8 окт. 2026 г.6Minecraft: 26.1fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.21.11)
1.3.3+1.21.11-fabric8 окт. 2026 г.73Minecraft: 1.21.11fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.21.10)
1.3.3+1.21.10-fabric8 окт. 2026 г.8Minecraft: 1.21.10fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.21.9)
1.3.3+1.21.9-fabric8 окт. 2026 г.0Minecraft: 1.21.9fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.21.8)
1.3.3+1.21.8-fabric8 окт. 2026 г.4Minecraft: 1.21.8fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.21.7)
1.3.3+1.21.7-fabric8 окт. 2026 г.0Minecraft: 1.21.7fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.21.6)
1.3.3+1.21.6-fabric8 окт. 2026 г.0Minecraft: 1.21.6fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.21.5)
1.3.3+1.21.5-fabric8 окт. 2026 г.0Minecraft: 1.21.5fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.21.4)
1.3.3+1.21.4-fabric8 окт. 2026 г.8Minecraft: 1.21.4fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.21.3)
1.3.3+1.21.3-fabric8 окт. 2026 г.0Minecraft: 1.21.3fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.21.2)
1.3.3+1.21.2-fabric8 окт. 2026 г.0Minecraft: 1.21.2fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.21.1)
1.3.3+1.21.1-fabric8 окт. 2026 г.38Minecraft: 1.21.1fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.21)
1.3.3+1.21-fabric8 окт. 2026 г.6Minecraft: 1.21fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.20.6)
1.3.3+1.20.6-fabric8 окт. 2026 г.2Minecraft: 1.20.6fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.20.5)
1.3.3+1.20.5-fabric8 окт. 2026 г.2Minecraft: 1.20.5fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.20.4)
1.3.3+1.20.4-fabric8 окт. 2026 г.6Minecraft: 1.20.4fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.20.3)
1.3.3+1.20.3-fabric8 окт. 2026 г.2Minecraft: 1.20.3fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.20.2)
1.3.3+1.20.2-fabric8 окт. 2026 г.4Minecraft: 1.20.2fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.20.1)
1.3.3+1.20.1-fabric8 окт. 2026 г.14Minecraft: 1.20.1fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.3 (Fabric 1.20)
1.3.3+1.20-fabric8 окт. 2026 г.2Minecraft: 1.20fabric
Что нового
Changelog: [Retromod 1.3.3](https://bownlux.dev/retromod/changelog.html#133-october-7-2026)
release
Retromod 1.3.2 (NeoForge 26.3)
1.3.2+26.3-neoforge30 сент. 2026 г.160Minecraft: 26.3neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 26.2)
1.3.2+26.2-neoforge30 сент. 2026 г.76Minecraft: 26.2neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 26.1.2)
1.3.2+26.1.2-neoforge30 сент. 2026 г.70Minecraft: 26.1.2neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 26.1.1)
1.3.2+26.1.1-neoforge30 сент. 2026 г.24Minecraft: 26.1.1neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 26.1)
1.3.2+26.1-neoforge30 сент. 2026 г.29Minecraft: 26.1neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.21.11)
1.3.2+1.21.11-neoforge30 сент. 2026 г.47Minecraft: 1.21.11neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.21.10)
1.3.2+1.21.10-neoforge30 сент. 2026 г.36Minecraft: 1.21.10neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.21.9)
1.3.2+1.21.9-neoforge30 сент. 2026 г.34Minecraft: 1.21.9neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.21.8)
1.3.2+1.21.8-neoforge30 сент. 2026 г.30Minecraft: 1.21.8neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.21.7)
1.3.2+1.21.7-neoforge30 сент. 2026 г.38Minecraft: 1.21.7neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.21.6)
1.3.2+1.21.6-neoforge30 сент. 2026 г.3Minecraft: 1.21.6neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.21.5)
1.3.2+1.21.5-neoforge30 сент. 2026 г.0Minecraft: 1.21.5neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.21.4)
1.3.2+1.21.4-neoforge30 сент. 2026 г.9Minecraft: 1.21.4neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.21.3)
1.3.2+1.21.3-neoforge30 сент. 2026 г.2Minecraft: 1.21.3neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.21.2)
1.3.2+1.21.2-neoforge30 сент. 2026 г.0Minecraft: 1.21.2neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.21.1)
1.3.2+1.21.1-neoforge30 сент. 2026 г.278Minecraft: 1.21.1neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.21)
1.3.2+1.21-neoforge30 сент. 2026 г.0Minecraft: 1.21neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.20.6)
1.3.2+1.20.6-neoforge30 сент. 2026 г.4Minecraft: 1.20.6neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.20.5)
1.3.2+1.20.5-neoforge30 сент. 2026 г.2Minecraft: 1.20.5neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.20.4)
1.3.2+1.20.4-neoforge30 сент. 2026 г.0Minecraft: 1.20.4neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.20.3)
1.3.2+1.20.3-neoforge30 сент. 2026 г.1Minecraft: 1.20.3neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.20.2)
1.3.2+1.20.2-neoforge30 сент. 2026 г.0Minecraft: 1.20.2neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (NeoForge 1.20.1)
1.3.2+1.20.1-neoforge30 сент. 2026 г.10Minecraft: 1.20.1neoforge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 26.3)
1.3.2+26.3-forge30 сент. 2026 г.69Minecraft: 26.3forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 26.2)
1.3.2+26.2-forge30 сент. 2026 г.45Minecraft: 26.2forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 26.1.2)
1.3.2+26.1.2-forge30 сент. 2026 г.38Minecraft: 26.1.2forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 26.1.1)
1.3.2+26.1.1-forge30 сент. 2026 г.1Minecraft: 26.1.1forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 26.1)
1.3.2+26.1-forge30 сент. 2026 г.34Minecraft: 26.1forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.21.11)
1.3.2+1.21.11-forge30 сент. 2026 г.41Minecraft: 1.21.11forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.21.10)
1.3.2+1.21.10-forge30 сент. 2026 г.0Minecraft: 1.21.10forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.21.9)
1.3.2+1.21.9-forge30 сент. 2026 г.26Minecraft: 1.21.9forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.21.8)
1.3.2+1.21.8-forge30 сент. 2026 г.0Minecraft: 1.21.8forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.21.7)
1.3.2+1.21.7-forge30 сент. 2026 г.1Minecraft: 1.21.7forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.21.6)
1.3.2+1.21.6-forge30 сент. 2026 г.25Minecraft: 1.21.6forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.21.5)
1.3.2+1.21.5-forge30 сент. 2026 г.5Minecraft: 1.21.5forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.21.4)
1.3.2+1.21.4-forge30 сент. 2026 г.5Minecraft: 1.21.4forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.21.3)
1.3.2+1.21.3-forge30 сент. 2026 г.0Minecraft: 1.21.3forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.21.2)
1.3.2+1.21.2-forge30 сент. 2026 г.0Minecraft: 1.21.2forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.21.1)
1.3.2+1.21.1-forge30 сент. 2026 г.22Minecraft: 1.21.1forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.21)
1.3.2+1.21-forge30 сент. 2026 г.2Minecraft: 1.21forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.20.6)
1.3.2+1.20.6-forge30 сент. 2026 г.9Minecraft: 1.20.6forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.20.5)
1.3.2+1.20.5-forge30 сент. 2026 г.1Minecraft: 1.20.5forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.20.4)
1.3.2+1.20.4-forge30 сент. 2026 г.2Minecraft: 1.20.4forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.20.3)
1.3.2+1.20.3-forge30 сент. 2026 г.0Minecraft: 1.20.3forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.20.2)
1.3.2+1.20.2-forge30 сент. 2026 г.2Minecraft: 1.20.2forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.20.1)
1.3.2+1.20.1-forge30 сент. 2026 г.167Minecraft: 1.20.1forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Forge 1.20)
1.3.2+1.20-forge30 сент. 2026 г.1Minecraft: 1.20forge
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 26.3)
1.3.2+26.3-fabric30 сент. 2026 г.884Minecraft: 26.3fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 26.2)
1.3.2+26.2-fabric30 сент. 2026 г.456Minecraft: 26.2fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 26.1.2)
1.3.2+26.1.2-fabric30 сент. 2026 г.109Minecraft: 26.1.2fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 26.1.1)
1.3.2+26.1.1-fabric30 сент. 2026 г.9Minecraft: 26.1.1fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 26.1)
1.3.2+26.1-fabric30 сент. 2026 г.36Minecraft: 26.1fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.21.11)
1.3.2+1.21.11-fabric30 сент. 2026 г.177Minecraft: 1.21.11fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.21.10)
1.3.2+1.21.10-fabric30 сент. 2026 г.30Minecraft: 1.21.10fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.21.9)
1.3.2+1.21.9-fabric30 сент. 2026 г.27Minecraft: 1.21.9fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.21.8)
1.3.2+1.21.8-fabric30 сент. 2026 г.18Minecraft: 1.21.8fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.21.7)
1.3.2+1.21.7-fabric30 сент. 2026 г.27Minecraft: 1.21.7fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.21.6)
1.3.2+1.21.6-fabric30 сент. 2026 г.13Minecraft: 1.21.6fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.21.5)
1.3.2+1.21.5-fabric30 сент. 2026 г.22Minecraft: 1.21.5fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.21.4)
1.3.2+1.21.4-fabric30 сент. 2026 г.20Minecraft: 1.21.4fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.21.3)
1.3.2+1.21.3-fabric30 сент. 2026 г.1Minecraft: 1.21.3fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.21.2)
1.3.2+1.21.2-fabric30 сент. 2026 г.1Minecraft: 1.21.2fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.21.1)
1.3.2+1.21.1-fabric30 сент. 2026 г.72Minecraft: 1.21.1fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.21)
1.3.2+1.21-fabric30 сент. 2026 г.8Minecraft: 1.21fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.20.6)
1.3.2+1.20.6-fabric30 сент. 2026 г.3Minecraft: 1.20.6fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.20.5)
1.3.2+1.20.5-fabric30 сент. 2026 г.1Minecraft: 1.20.5fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.20.4)
1.3.2+1.20.4-fabric30 сент. 2026 г.10Minecraft: 1.20.4fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.20.3)
1.3.2+1.20.3-fabric30 сент. 2026 г.1Minecraft: 1.20.3fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.20.2)
1.3.2+1.20.2-fabric30 сент. 2026 г.2Minecraft: 1.20.2fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.20.1)
1.3.2+1.20.1-fabric30 сент. 2026 г.24Minecraft: 1.20.1fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.2 (Fabric 1.20)
1.3.2+1.20-fabric30 сент. 2026 г.3Minecraft: 1.20fabric
Что нового
Patch release. Adds Forge 26.3, follows 26.3's new key numbering, and fixes several bugs found in reports and in the published jars.
### Added
- Builds Retromod for Forge 26.3 (Forge 66). Carries the Forge API members 66 renamed without changing their meaning: `EntityTeleportEvent.EnderEntity` to `EntityRandom` and the `ModifiableBiomeInfo.BiomeInfo.Builder` getters.
- Translates key codes on 26.3, which moved input from GLFW to SDL and renumbered every key and mouse button. Keybind defaults, and literal codes passed to `isKeyDown` and `InputConstants.Type.getOrCreate`, become the host's code for the same key. A code the mod reads at runtime is already the host's and is left alone. `Type.KEYSYM`, which 26.3 replaced with `KEYBOARD`, and `isKeyDown(Window, int)`, which lost its window parameter, no longer crash.
- Keeps item tooltips from mods built before 1.21.5. `appendHoverText` moved to a `TooltipDisplay` and a `Consumer` at 1.21.5, so an old override was never called; a bridge now collects its lines and forwards them.
- Carries the authlib 10 renames on 26.3, including `MinecraftSessionService` to `SessionService` and the profile types from `yggdrasil` to `services`.
- Renames `DataPackRegistryEvent.NewRegistry` to `NewDatapackRegistryEvent` on NeoForge 26.3 builds that removed it. The change landed inside the 26.3 betas, so Retromod checks the running NeoForge rather than the Minecraft version.
### Fixed
- Stops a polyfill for the pre-1.20 `GuiComponent` from capturing modern `GuiGraphics.drawString` calls. Its key named a signature the old class never had, and the 1.20 class redirect turned it into the modern method, which returns `void` from 1.21.6. A 1.21.6 to 1.21.11 Fabric mod that drew text on a 26.x host failed at startup with `VerifyError`.
- Starts Retromod on NeoForge 1.21.1 again. Gson pulled `error_prone_annotations` into the release jar, NeoForge 1.21.1 ships the same package as a module, and the split package stopped the game before it wrote a log. The dependency is excluded, and `build-all.sh` now refuses a jar that bundles classes outside Retromod's own packages or a `module-info.class`.
- Reads `FMLEnvironment.dist` as a field on NeoForge before 1.21.9. The Forge to NeoForge bridge always called `getDist()`, which arrived at 1.21.9, so a Forge mod checking its side failed on 1.21.1 with `NoSuchMethodError`.
- Marks an old dependency declared only with `mandatory` as `type = "optional"`. NeoForge has ignored `mandatory` since 1.20.2 and treats a dependency without a `type` as required, so a mod's optional companion stopped it from loading.
- Remaps SRG names in Forge refmaps that carry no owner, which is how Forge stores accessor, invoker, and shadow targets. They stayed SRG on Mojang-named hosts, so an `@Accessor` failed with `No candidates were found matching f_20888_`. An SRG-named Forge host keeps them, because the owner is needed to find its own SRG name.
- Redirects `new Identifier(String)` to `Identifier.parse` for Fabric mods on 1.21 and newer. Only the two-argument constructor was redirected, and discovery declined because 1.21 has three static `(String)` factories, so the call failed with `NoSuchMethodError`. Discovery now leaves a constructor alone on hosts where it is still public.
- Renames six Fabric API interface methods that 26.1 renamed, including `HudElement.render` to `extractRenderState` and `ItemVariant.withComponentChanges` to `withComponents`. A lambda is bound by method name at runtime, so a HUD registered as a lambda threw `AbstractMethodError` on its first frame.
- Stops the release jar's Gson relocation from rewriting Retromod's references to the mods' Gson. Every published 1.3.x jar named Retromod's private copy there, which broke the reload-listener bridge with a `VerifyError` and silently disabled the Patchouli Gson repair. A test now fails if such a literal comes back.
- Stamps the registry id for NeoForge mods that register through `DeferredRegister.createBlocks(...)` or `createItems(...)`. Their typed `register` overloads never matched the bridge, so the standard 1.21 idiom still failed with `Block id not set`.
- Fixes custom swords, tools, and armor on 1.21.5 and newer. An older class redirect sent `SwordItem` and its siblings to `Item` before the removed-base rebase could act, so the class extended `Item` and called a constructor that does not exist. Every reference to a removed item base, including `instanceof` checks and parameter types, now names its generated stand-in. `AxeItem`, `ShovelItem`, and `HoeItem` are no longer redirected on hosts that still have them.
- Rebases each removed item base on every host that removed it, not only from 26.1. A 1.20.1 music disc mod failed on 1.21.1 with `NoClassDefFoundError: RecordItem`.
- Rebuilds NeoForge keybinds that pass an `IKeyConflictContext` and a text category. NeoForge removed those constructors at 1.21.9, and they failed with `NoSuchMethodError` on 26.x.
- Lists a mod's keybinds under the mod's own category again. The bridge called `Identifier.of`, which 26.x does not have, so every mod category fell back to Miscellaneous.
- Calls `Component.literal` as an interface method in the pre-1.19.3 creative tab bridge, which failed with `IncompatibleClassChangeError`.
- Relaxes a sibling mod's version range on every host, not only on 26.x. An optional dependency is still range-checked, and Farmer's Delight 1.3.4 sorts below a range written as `1.20.1-1.2.4`. An `incompatible` or `discouraged` dependency keeps its range, which lists the versions to avoid.
- Relaxes a bounded `loaderVersion` such as `[47,48)` on Forge as well as NeoForge, and in offline transforms.
- Corrects the Windows build's expected artifact counts, which still described the release before 26.3.
### Changed
- Documents the 500 MB unpacked jar limit, which leaves a very large mod untransformed.
- Truncates crash logs pasted into compatibility reports to 400 characters in the published entry. The card links the full issue.
A mod that compares raw key codes itself, for example in `keyPressed`, still sees SDL numbers on 26.3 and needs a port. So do `ToggleKeyMapping`, a `KeyMapping` subclass's own constructor, and Forge's conflict-context keybind constructors, whose defaults keep their GLFW numbers. The creative tab bridge still uses Mojang member names, so it does not work on Forge hosts that run SRG names, 1.19.3 through 1.20.4. A custom tool tier that implements `Tier` still fails, because `Tier` became the `ToolMaterial` record at 1.21.2. A method one mod exposes to another with a removed item base in its signature no longer links across the two, because each mod carries its own stand-in. A NeoForge mod transformed in place keeps the datapack registry event its NeoForge build had, so updating NeoForge across that change needs the mod transformed again. `neighborChanged` overrides are not bridged.
release
Retromod 1.3.1 (NeoForge 26.3)
1.3.1+26.3-neoforge20 сент. 2026 г.98Minecraft: 26.3neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 26.2)
1.3.1+26.2-neoforge20 сент. 2026 г.88Minecraft: 26.2neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 26.1.2)
1.3.1+26.1.2-neoforge20 сент. 2026 г.69Minecraft: 26.1.2neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 26.1.1)
1.3.1+26.1.1-neoforge20 сент. 2026 г.3Minecraft: 26.1.1neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 26.1)
1.3.1+26.1-neoforge20 сент. 2026 г.4Minecraft: 26.1neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.21.11)
1.3.1+1.21.11-neoforge20 сент. 2026 г.14Minecraft: 1.21.11neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.21.10)
1.3.1+1.21.10-neoforge20 сент. 2026 г.32Minecraft: 1.21.10neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.21.9)
1.3.1+1.21.9-neoforge20 сент. 2026 г.0Minecraft: 1.21.9neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.21.8)
1.3.1+1.21.8-neoforge20 сент. 2026 г.33Minecraft: 1.21.8neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.21.7)
1.3.1+1.21.7-neoforge20 сент. 2026 г.16Minecraft: 1.21.7neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.21.6)
1.3.1+1.21.6-neoforge20 сент. 2026 г.16Minecraft: 1.21.6neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.21.5)
1.3.1+1.21.5-neoforge20 сент. 2026 г.55Minecraft: 1.21.5neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.21.4)
1.3.1+1.21.4-neoforge20 сент. 2026 г.27Minecraft: 1.21.4neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.21.3)
1.3.1+1.21.3-neoforge20 сент. 2026 г.1Minecraft: 1.21.3neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.21.2)
1.3.1+1.21.2-neoforge20 сент. 2026 г.1Minecraft: 1.21.2neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.21.1)
1.3.1+1.21.1-neoforge20 сент. 2026 г.217Minecraft: 1.21.1neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.21)
1.3.1+1.21-neoforge20 сент. 2026 г.1Minecraft: 1.21neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.20.6)
1.3.1+1.20.6-neoforge20 сент. 2026 г.0Minecraft: 1.20.6neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.20.5)
1.3.1+1.20.5-neoforge20 сент. 2026 г.1Minecraft: 1.20.5neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.20.4)
1.3.1+1.20.4-neoforge20 сент. 2026 г.1Minecraft: 1.20.4neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.20.3)
1.3.1+1.20.3-neoforge20 сент. 2026 г.2Minecraft: 1.20.3neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.20.2)
1.3.1+1.20.2-neoforge20 сент. 2026 г.3Minecraft: 1.20.2neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (NeoForge 1.20.1)
1.3.1+1.20.1-neoforge20 сент. 2026 г.13Minecraft: 1.20.1neoforge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 26.2)
1.3.1+26.2-forge20 сент. 2026 г.47Minecraft: 26.2forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 26.1.2)
1.3.1+26.1.2-forge20 сент. 2026 г.24Minecraft: 26.1.2forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 26.1.1)
1.3.1+26.1.1-forge20 сент. 2026 г.30Minecraft: 26.1.1forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 26.1)
1.3.1+26.1-forge20 сент. 2026 г.32Minecraft: 26.1forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.21.11)
1.3.1+1.21.11-forge20 сент. 2026 г.39Minecraft: 1.21.11forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.21.10)
1.3.1+1.21.10-forge20 сент. 2026 г.1Minecraft: 1.21.10forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.21.9)
1.3.1+1.21.9-forge20 сент. 2026 г.0Minecraft: 1.21.9forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.21.8)
1.3.1+1.21.8-forge20 сент. 2026 г.1Minecraft: 1.21.8forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.21.7)
1.3.1+1.21.7-forge20 сент. 2026 г.1Minecraft: 1.21.7forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.21.6)
1.3.1+1.21.6-forge20 сент. 2026 г.0Minecraft: 1.21.6forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.21.5)
1.3.1+1.21.5-forge20 сент. 2026 г.1Minecraft: 1.21.5forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.21.4)
1.3.1+1.21.4-forge20 сент. 2026 г.2Minecraft: 1.21.4forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.21.3)
1.3.1+1.21.3-forge20 сент. 2026 г.1Minecraft: 1.21.3forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.21.2)
1.3.1+1.21.2-forge20 сент. 2026 г.1Minecraft: 1.21.2forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.21.1)
1.3.1+1.21.1-forge20 сент. 2026 г.27Minecraft: 1.21.1forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.21)
1.3.1+1.21-forge20 сент. 2026 г.3Minecraft: 1.21forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.20.6)
1.3.1+1.20.6-forge20 сент. 2026 г.6Minecraft: 1.20.6forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.20.5)
1.3.1+1.20.5-forge20 сент. 2026 г.1Minecraft: 1.20.5forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.20.4)
1.3.1+1.20.4-forge20 сент. 2026 г.0Minecraft: 1.20.4forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.20.3)
1.3.1+1.20.3-forge20 сент. 2026 г.2Minecraft: 1.20.3forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.20.2)
1.3.1+1.20.2-forge20 сент. 2026 г.3Minecraft: 1.20.2forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.20.1)
1.3.1+1.20.1-forge20 сент. 2026 г.112Minecraft: 1.20.1forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Forge 1.20)
1.3.1+1.20-forge20 сент. 2026 г.6Minecraft: 1.20forge
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 26.3)
1.3.1+26.3-fabric20 сент. 2026 г.581Minecraft: 26.3fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 26.2)
1.3.1+26.2-fabric20 сент. 2026 г.393Minecraft: 26.2fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 26.1.2)
1.3.1+26.1.2-fabric20 сент. 2026 г.100Minecraft: 26.1.2fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 26.1.1)
1.3.1+26.1.1-fabric20 сент. 2026 г.3Minecraft: 26.1.1fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 26.1)
1.3.1+26.1-fabric20 сент. 2026 г.17Minecraft: 26.1fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.21.11)
1.3.1+1.21.11-fabric20 сент. 2026 г.142Minecraft: 1.21.11fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.21.10)
1.3.1+1.21.10-fabric20 сент. 2026 г.6Minecraft: 1.21.10fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.21.9)
1.3.1+1.21.9-fabric20 сент. 2026 г.9Minecraft: 1.21.9fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.21.8)
1.3.1+1.21.8-fabric20 сент. 2026 г.7Minecraft: 1.21.8fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.21.7)
1.3.1+1.21.7-fabric20 сент. 2026 г.1Minecraft: 1.21.7fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.21.6)
1.3.1+1.21.6-fabric20 сент. 2026 г.3Minecraft: 1.21.6fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.21.5)
1.3.1+1.21.5-fabric20 сент. 2026 г.1Minecraft: 1.21.5fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.21.4)
1.3.1+1.21.4-fabric20 сент. 2026 г.16Minecraft: 1.21.4fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.21.3)
1.3.1+1.21.3-fabric20 сент. 2026 г.2Minecraft: 1.21.3fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.21.2)
1.3.1+1.21.2-fabric20 сент. 2026 г.2Minecraft: 1.21.2fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.21.1)
1.3.1+1.21.1-fabric20 сент. 2026 г.55Minecraft: 1.21.1fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.21)
1.3.1+1.21-fabric20 сент. 2026 г.10Minecraft: 1.21fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.20.6)
1.3.1+1.20.6-fabric20 сент. 2026 г.1Minecraft: 1.20.6fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.20.5)
1.3.1+1.20.5-fabric20 сент. 2026 г.0Minecraft: 1.20.5fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.20.4)
1.3.1+1.20.4-fabric20 сент. 2026 г.11Minecraft: 1.20.4fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.20.3)
1.3.1+1.20.3-fabric20 сент. 2026 г.1Minecraft: 1.20.3fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.20.2)
1.3.1+1.20.2-fabric20 сент. 2026 г.3Minecraft: 1.20.2fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.20.1)
1.3.1+1.20.1-fabric20 сент. 2026 г.33Minecraft: 1.20.1fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.1 (Fabric 1.20)
1.3.1+1.20-fabric20 сент. 2026 г.1Minecraft: 1.20fabric
Что нового
Patch release. Fixes the reason most Fabric mods could not be used on 26.x, plus two smaller gaps found after 1.3.0 shipped.
### Fixed
- Loads a Fabric mod that adds blocks or items on 26.x. 26.x refuses to construct a block until its registry id is stamped on its `Properties`. A NeoForge mod gets that from its deferred register, and Retromod has bridged it there since 1.2.0. Fabric has no deferred register: the mod calls `Registry.register(registry, id, block)` and builds the block inside the argument list, so nothing ran before the constructor and every such mod died at its own class init with `NullPointerException: Block id not set`. The id is still reachable, because arguments evaluate left to right, so it is stamped where it is produced and read back while the block is built. The same is done for items, which fail the same way one line later. A `Properties` held in a shared field is also stamped where it is passed to the constructor, since that one was never created inside a registration to begin with.
- Translates `GlassBlock` to `TransparentBlock`. Only the 1.12.2 spelling had been repointed, so a mod naming the modern one still failed with `NoClassDefFoundError`.
- Stops `gaps` reporting that it ignored `--target`, which it honours.
- Loads NeoForge mods that build blocks from `BlockBehaviour.Properties.of()` on 26.x again. The Fabric `FabricBlockSettings` polyfill left a self-referencing alias on `Properties.of()` that replaced the registry id bridge, so the mod failed at registration with `Block id not set`. Only the in-game NeoForge path loads Fabric polyfills, so offline transforms were unaffected.
- Repairs 1.21.1 NeoForge and Forge mods that use `ItemInteractionResult`. The class merged into `InteractionResult` at 1.21.2 and only the class name was repointed, so the first right click on such a block threw `NoSuchFieldError: PASS_TO_DEFAULT_BLOCK_INTERACTION`. The constants now resolve to their typed 1.21.2 fields, and `sidedSuccess` and `consumesAction` are bridged.
- Keeps 1.21.1 block item names. A `BlockItem` used its block's description id until 1.21.2, so a mod whose lang file has only `block.` keys showed raw `item.` keys for every block item.
- Converts pre-1.21.2 recipe ingredients such as `{"item": "minecraft:stick"}` to the string form. 26.2 skipped those recipes; 26.3 loads recipes with the registries, so one old recipe stopped the world from loading.
- Renames the `recipe_unlocked` advancement condition `recipe` to `recipes` for 26.3.
- Drops a recipe advancement that rewards a recipe from its own mod that the mod does not ship. Older versions ignored the dangling id; 26.3 refuses to load the world.
- Bridges pre-1.21.2 `updateShape` and `getOcclusionShape` overrides to the new signatures. The old overrides still loaded but were never called, so blocks that connect to their neighbors stopped connecting.
Found by running mods rather than by a report. Other 1.21.2 signature changes are not bridged yet: `neighborChanged` now receives an `Orientation` instead of the neighbor position, and `appendHoverText` takes a `TooltipDisplay` and a `Consumer`, so those overrides still go uncalled. A Fabric mod can still fail for reasons this does not touch: a removed Fabric API class such as the old `FuelRegistry` is a rewritten API rather than a rename, `ArmorMaterial` became a record so a mod implementing it hits `IncompatibleClassChangeError`, and `Feature` became an interface at 26.3 so a worldgen mod extending it needs a port.
release
Retromod 1.3.0 (NeoForge 26.3)
1.3.0+26.3-neoforge15 сент. 2026 г.61Minecraft: 26.3neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 26.2)
1.3.0+26.2-neoforge15 сент. 2026 г.36Minecraft: 26.2neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 26.1.2)
1.3.0+26.1.2-neoforge15 сент. 2026 г.47Minecraft: 26.1.2neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 26.1.1)
1.3.0+26.1.1-neoforge15 сент. 2026 г.5Minecraft: 26.1.1neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 26.1)
1.3.0+26.1-neoforge15 сент. 2026 г.7Minecraft: 26.1neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.21.11)
1.3.0+1.21.11-neoforge15 сент. 2026 г.9Minecraft: 1.21.11neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.21.10)
1.3.0+1.21.10-neoforge15 сент. 2026 г.6Minecraft: 1.21.10neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.21.9)
1.3.0+1.21.9-neoforge15 сент. 2026 г.2Minecraft: 1.21.9neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.21.8)
1.3.0+1.21.8-neoforge15 сент. 2026 г.15Minecraft: 1.21.8neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.21.7)
1.3.0+1.21.7-neoforge15 сент. 2026 г.4Minecraft: 1.21.7neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.21.6)
1.3.0+1.21.6-neoforge15 сент. 2026 г.4Minecraft: 1.21.6neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.21.5)
1.3.0+1.21.5-neoforge15 сент. 2026 г.13Minecraft: 1.21.5neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.21.4)
1.3.0+1.21.4-neoforge15 сент. 2026 г.15Minecraft: 1.21.4neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.21.3)
1.3.0+1.21.3-neoforge15 сент. 2026 г.6Minecraft: 1.21.3neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.21.2)
1.3.0+1.21.2-neoforge15 сент. 2026 г.4Minecraft: 1.21.2neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.21.1)
1.3.0+1.21.1-neoforge15 сент. 2026 г.157Minecraft: 1.21.1neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.21)
1.3.0+1.21-neoforge15 сент. 2026 г.6Minecraft: 1.21neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.20.6)
1.3.0+1.20.6-neoforge15 сент. 2026 г.5Minecraft: 1.20.6neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.20.5)
1.3.0+1.20.5-neoforge15 сент. 2026 г.7Minecraft: 1.20.5neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.20.4)
1.3.0+1.20.4-neoforge15 сент. 2026 г.6Minecraft: 1.20.4neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.20.3)
1.3.0+1.20.3-neoforge15 сент. 2026 г.9Minecraft: 1.20.3neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.20.2)
1.3.0+1.20.2-neoforge15 сент. 2026 г.6Minecraft: 1.20.2neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (NeoForge 1.20.1)
1.3.0+1.20.1-neoforge15 сент. 2026 г.4Minecraft: 1.20.1neoforge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 26.2)
1.3.0+26.2-forge15 сент. 2026 г.19Minecraft: 26.2forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 26.1.2)
1.3.0+26.1.2-forge15 сент. 2026 г.13Minecraft: 26.1.2forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 26.1.1)
1.3.0+26.1.1-forge15 сент. 2026 г.46Minecraft: 26.1.1forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 26.1)
1.3.0+26.1-forge15 сент. 2026 г.7Minecraft: 26.1forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.21.11)
1.3.0+1.21.11-forge15 сент. 2026 г.8Minecraft: 1.21.11forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.21.10)
1.3.0+1.21.10-forge15 сент. 2026 г.7Minecraft: 1.21.10forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.21.9)
1.3.0+1.21.9-forge15 сент. 2026 г.10Minecraft: 1.21.9forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.21.8)
1.3.0+1.21.8-forge15 сент. 2026 г.4Minecraft: 1.21.8forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.21.7)
1.3.0+1.21.7-forge15 сент. 2026 г.5Minecraft: 1.21.7forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.21.6)
1.3.0+1.21.6-forge15 сент. 2026 г.0Minecraft: 1.21.6forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.21.5)
1.3.0+1.21.5-forge15 сент. 2026 г.7Minecraft: 1.21.5forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.21.4)
1.3.0+1.21.4-forge15 сент. 2026 г.10Minecraft: 1.21.4forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.21.3)
1.3.0+1.21.3-forge15 сент. 2026 г.5Minecraft: 1.21.3forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.21.2)
1.3.0+1.21.2-forge15 сент. 2026 г.7Minecraft: 1.21.2forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.21.1)
1.3.0+1.21.1-forge15 сент. 2026 г.13Minecraft: 1.21.1forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.21)
1.3.0+1.21-forge15 сент. 2026 г.6Minecraft: 1.21forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.20.6)
1.3.0+1.20.6-forge15 сент. 2026 г.12Minecraft: 1.20.6forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.20.5)
1.3.0+1.20.5-forge15 сент. 2026 г.4Minecraft: 1.20.5forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.20.4)
1.3.0+1.20.4-forge15 сент. 2026 г.4Minecraft: 1.20.4forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.20.3)
1.3.0+1.20.3-forge15 сент. 2026 г.7Minecraft: 1.20.3forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.20.2)
1.3.0+1.20.2-forge15 сент. 2026 г.3Minecraft: 1.20.2forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.20.1)
1.3.0+1.20.1-forge15 сент. 2026 г.60Minecraft: 1.20.1forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Forge 1.20)
1.3.0+1.20-forge15 сент. 2026 г.10Minecraft: 1.20forge
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 26.3)
1.3.0+26.3-fabric15 сент. 2026 г.371Minecraft: 26.3fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 26.2)
1.3.0+26.2-fabric15 сент. 2026 г.261Minecraft: 26.2fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 26.1.2)
1.3.0+26.1.2-fabric15 сент. 2026 г.56Minecraft: 26.1.2fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 26.1.1)
1.3.0+26.1.1-fabric15 сент. 2026 г.7Minecraft: 26.1.1fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 26.1)
1.3.0+26.1-fabric15 сент. 2026 г.1Minecraft: 26.1fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.21.11)
1.3.0+1.21.11-fabric15 сент. 2026 г.95Minecraft: 1.21.11fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.21.10)
1.3.0+1.21.10-fabric15 сент. 2026 г.7Minecraft: 1.21.10fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.21.9)
1.3.0+1.21.9-fabric15 сент. 2026 г.0Minecraft: 1.21.9fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.21.8)
1.3.0+1.21.8-fabric15 сент. 2026 г.3Minecraft: 1.21.8fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.21.7)
1.3.0+1.21.7-fabric15 сент. 2026 г.2Minecraft: 1.21.7fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.21.6)
1.3.0+1.21.6-fabric15 сент. 2026 г.0Minecraft: 1.21.6fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.21.5)
1.3.0+1.21.5-fabric15 сент. 2026 г.2Minecraft: 1.21.5fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.21.4)
1.3.0+1.21.4-fabric15 сент. 2026 г.11Minecraft: 1.21.4fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.21.3)
1.3.0+1.21.3-fabric15 сент. 2026 г.4Minecraft: 1.21.3fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.21.2)
1.3.0+1.21.2-fabric15 сент. 2026 г.5Minecraft: 1.21.2fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.21.1)
1.3.0+1.21.1-fabric15 сент. 2026 г.38Minecraft: 1.21.1fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.21)
1.3.0+1.21-fabric15 сент. 2026 г.3Minecraft: 1.21fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.20.6)
1.3.0+1.20.6-fabric15 сент. 2026 г.6Minecraft: 1.20.6fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.20.5)
1.3.0+1.20.5-fabric15 сент. 2026 г.2Minecraft: 1.20.5fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.20.4)
1.3.0+1.20.4-fabric15 сент. 2026 г.7Minecraft: 1.20.4fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.20.3)
1.3.0+1.20.3-fabric15 сент. 2026 г.2Minecraft: 1.20.3fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.20.2)
1.3.0+1.20.2-fabric15 сент. 2026 г.4Minecraft: 1.20.2fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.20.1)
1.3.0+1.20.1-fabric15 сент. 2026 г.19Minecraft: 1.20.1fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
release
Retromod 1.3.0 (Fabric 1.20)
1.3.0+1.20-fabric15 сент. 2026 г.6Minecraft: 1.20fabric
Что нового
**Stable release.** Promoted from `1.3.0-rc.2`, with Minecraft 26.3 support added on release day and the fixes listed below found by scanning a corpus of widely used mods rather than waiting for reports. The 1.3.0 line ran `snapshot.1` through `snapshot.10`, then `rc.1` and `rc.2`; the sections below have the detail.
Headline work over 1.2.0: mods that extend a class Minecraft deleted now load, across the tool, armour, disc and screen families; the intermediary table was audited against the game's own files and 40 misdirected mappings corrected; Mixin signature repair became exact and conservative; and Forge, NeoForge and Fabric were brought level on repairs that had silently been running on only one of them.
### Added
- Minecraft 26.3 support for Fabric and NeoForge, verified against the released 26.3 client. Forge is not included for 26.3 because Forge has published no build for it; the Forge column stops at 26.2 and will follow when one appears.
### Fixed
- Stops rewriting `PoseStack`. A bridge for one JEI method was registered as a class redirect, which is global, so it rewrote every `PoseStack` in the mod including the 3D stack an entity renderer uses. The 26.1 move then carried the result on to `GuiGraphicsExtractor`, where an ordinary `pushPose` does not exist, so rendering died the first time it ran. Found in 12 of 13 widely used mods, about 500 call sites.
- Rebases a removed base class on Forge as well. Forge assembles its own list of the shared 26.1 adaptations rather than calling the common one, so it reached none of the removed-base bridges: a Forge mod with a custom tool, armour piece or music disc transformed correctly through the CLI and then failed at its `extends` in game.
- Reads game rules on every loader. 26.1 renamed all 46 constants, and the repair ran only on Fabric, on the reasoning that a Mojang-named loader already carries the current names. That is true of a mod built after 26.1 and of nothing older.
- Loads a mod that extends `EffectRenderingInventoryScreen`, `AbstractProjectileDispenseBehavior`, or any of twenty removed item classes including the whole tool and armour hierarchy.
- Restores the class move for `GameRules`, whose row had been deleted while the row for its nested `VisitorCaller` stayed.
- Renames `ForgeSpawnEggItem` to vanilla's `SpawnEggItem`, which Forge dropped once vanilla could do the same job.
- Stops reporting that a mod cannot load when it transforms fine. A class move can be a chain, and the check ran against a name that was still mid-chain, so every mod with a custom advancement trigger was reported as unloadable.
- Reads pack formats on a pre-release host, instead of turning pack transformation off for the whole session.
- Translates `Tier` and its `Tiers` constants to `ToolMaterial`.
Known 26.3 limitation, found on release day: `Feature` became an interface at 26.3, having been an abstract class through 26.2. A worldgen mod that writes `extends Feature` cannot be rebased onto it, so those mods need a real port. Retromod reports this by name, saying that the class is an interface and belongs in `implements`, rather than letting it fail later with nothing pointing at the cause.
Some limits are unchanged and worth stating plainly. A mod whose rendering is built on `BlockEntityWithoutLevelRenderer`, `ItemOverrides` or `AgeableListModel` still needs a real port: 26.x rebuilt entity and item rendering around render states, and a stand-in base would link and then draw nothing. The same is true of a custom loot function serializer, which the codec rework removed outright. Retromod reports these by name rather than guessing.
beta
Retromod 1.3.0-rc.1 (NeoForge 26.2)
1.3.0-rc.1+26.2-neoforge8 сент. 2026 г.93Minecraft: 26.2neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 26.1.2)
1.3.0-rc.1+26.1.2-neoforge8 сент. 2026 г.33Minecraft: 26.1.2neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 26.1.1)
1.3.0-rc.1+26.1.1-neoforge8 сент. 2026 г.11Minecraft: 26.1.1neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 26.1)
1.3.0-rc.1+26.1-neoforge8 сент. 2026 г.10Minecraft: 26.1neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.21.11)
1.3.0-rc.1+1.21.11-neoforge8 сент. 2026 г.20Minecraft: 1.21.11neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.21.10)
1.3.0-rc.1+1.21.10-neoforge8 сент. 2026 г.3Minecraft: 1.21.10neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.21.9)
1.3.0-rc.1+1.21.9-neoforge8 сент. 2026 г.1Minecraft: 1.21.9neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.21.8)
1.3.0-rc.1+1.21.8-neoforge8 сент. 2026 г.6Minecraft: 1.21.8neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.21.7)
1.3.0-rc.1+1.21.7-neoforge8 сент. 2026 г.1Minecraft: 1.21.7neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.21.6)
1.3.0-rc.1+1.21.6-neoforge8 сент. 2026 г.1Minecraft: 1.21.6neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.21.5)
1.3.0-rc.1+1.21.5-neoforge8 сент. 2026 г.2Minecraft: 1.21.5neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.21.4)
1.3.0-rc.1+1.21.4-neoforge8 сент. 2026 г.9Minecraft: 1.21.4neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.21.3)
1.3.0-rc.1+1.21.3-neoforge8 сент. 2026 г.1Minecraft: 1.21.3neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.21.2)
1.3.0-rc.1+1.21.2-neoforge8 сент. 2026 г.3Minecraft: 1.21.2neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.21.1)
1.3.0-rc.1+1.21.1-neoforge8 сент. 2026 г.231Minecraft: 1.21.1neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.21)
1.3.0-rc.1+1.21-neoforge8 сент. 2026 г.2Minecraft: 1.21neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.20.6)
1.3.0-rc.1+1.20.6-neoforge8 сент. 2026 г.3Minecraft: 1.20.6neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.20.5)
1.3.0-rc.1+1.20.5-neoforge8 сент. 2026 г.3Minecraft: 1.20.5neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.20.4)
1.3.0-rc.1+1.20.4-neoforge8 сент. 2026 г.3Minecraft: 1.20.4neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.20.3)
1.3.0-rc.1+1.20.3-neoforge8 сент. 2026 г.3Minecraft: 1.20.3neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.20.2)
1.3.0-rc.1+1.20.2-neoforge8 сент. 2026 г.3Minecraft: 1.20.2neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (NeoForge 1.20.1)
1.3.0-rc.1+1.20.1-neoforge8 сент. 2026 г.10Minecraft: 1.20.1neoforge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 26.2)
1.3.0-rc.1+26.2-forge8 сент. 2026 г.21Minecraft: 26.2forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 26.1.2)
1.3.0-rc.1+26.1.2-forge8 сент. 2026 г.7Minecraft: 26.1.2forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 26.1.1)
1.3.0-rc.1+26.1.1-forge8 сент. 2026 г.2Minecraft: 26.1.1forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 26.1)
1.3.0-rc.1+26.1-forge8 сент. 2026 г.3Minecraft: 26.1forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.21.11)
1.3.0-rc.1+1.21.11-forge8 сент. 2026 г.8Minecraft: 1.21.11forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.21.10)
1.3.0-rc.1+1.21.10-forge8 сент. 2026 г.1Minecraft: 1.21.10forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.21.9)
1.3.0-rc.1+1.21.9-forge8 сент. 2026 г.1Minecraft: 1.21.9forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.21.8)
1.3.0-rc.1+1.21.8-forge8 сент. 2026 г.4Minecraft: 1.21.8forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.21.7)
1.3.0-rc.1+1.21.7-forge8 сент. 2026 г.2Minecraft: 1.21.7forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.21.6)
1.3.0-rc.1+1.21.6-forge8 сент. 2026 г.1Minecraft: 1.21.6forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.21.5)
1.3.0-rc.1+1.21.5-forge8 сент. 2026 г.1Minecraft: 1.21.5forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.21.4)
1.3.0-rc.1+1.21.4-forge8 сент. 2026 г.4Minecraft: 1.21.4forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.21.3)
1.3.0-rc.1+1.21.3-forge8 сент. 2026 г.3Minecraft: 1.21.3forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.21.2)
1.3.0-rc.1+1.21.2-forge8 сент. 2026 г.2Minecraft: 1.21.2forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.21.1)
1.3.0-rc.1+1.21.1-forge8 сент. 2026 г.13Minecraft: 1.21.1forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.21)
1.3.0-rc.1+1.21-forge8 сент. 2026 г.2Minecraft: 1.21forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.20.6)
1.3.0-rc.1+1.20.6-forge8 сент. 2026 г.4Minecraft: 1.20.6forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.20.5)
1.3.0-rc.1+1.20.5-forge8 сент. 2026 г.2Minecraft: 1.20.5forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.20.4)
1.3.0-rc.1+1.20.4-forge8 сент. 2026 г.3Minecraft: 1.20.4forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.20.3)
1.3.0-rc.1+1.20.3-forge8 сент. 2026 г.4Minecraft: 1.20.3forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.20.2)
1.3.0-rc.1+1.20.2-forge8 сент. 2026 г.1Minecraft: 1.20.2forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.20.1)
1.3.0-rc.1+1.20.1-forge8 сент. 2026 г.83Minecraft: 1.20.1forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Forge 1.20)
1.3.0-rc.1+1.20-forge8 сент. 2026 г.2Minecraft: 1.20forge
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 26.2)
1.3.0-rc.1+26.2-fabric8 сент. 2026 г.291Minecraft: 26.2fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 26.1.2)
1.3.0-rc.1+26.1.2-fabric8 сент. 2026 г.56Minecraft: 26.1.2fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 26.1.1)
1.3.0-rc.1+26.1.1-fabric8 сент. 2026 г.3Minecraft: 26.1.1fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 26.1)
1.3.0-rc.1+26.1-fabric8 сент. 2026 г.27Minecraft: 26.1fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.21.11)
1.3.0-rc.1+1.21.11-fabric8 сент. 2026 г.116Minecraft: 1.21.11fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.21.10)
1.3.0-rc.1+1.21.10-fabric8 сент. 2026 г.6Minecraft: 1.21.10fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.21.9)
1.3.0-rc.1+1.21.9-fabric8 сент. 2026 г.1Minecraft: 1.21.9fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.21.8)
1.3.0-rc.1+1.21.8-fabric8 сент. 2026 г.5Minecraft: 1.21.8fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.21.7)
1.3.0-rc.1+1.21.7-fabric8 сент. 2026 г.0Minecraft: 1.21.7fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.21.6)
1.3.0-rc.1+1.21.6-fabric8 сент. 2026 г.2Minecraft: 1.21.6fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.21.5)
1.3.0-rc.1+1.21.5-fabric8 сент. 2026 г.3Minecraft: 1.21.5fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.21.4)
1.3.0-rc.1+1.21.4-fabric8 сент. 2026 г.8Minecraft: 1.21.4fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.21.3)
1.3.0-rc.1+1.21.3-fabric8 сент. 2026 г.1Minecraft: 1.21.3fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.21.2)
1.3.0-rc.1+1.21.2-fabric8 сент. 2026 г.1Minecraft: 1.21.2fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.21.1)
1.3.0-rc.1+1.21.1-fabric8 сент. 2026 г.57Minecraft: 1.21.1fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.21)
1.3.0-rc.1+1.21-fabric8 сент. 2026 г.4Minecraft: 1.21fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.20.6)
1.3.0-rc.1+1.20.6-fabric8 сент. 2026 г.1Minecraft: 1.20.6fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.20.5)
1.3.0-rc.1+1.20.5-fabric8 сент. 2026 г.3Minecraft: 1.20.5fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.20.4)
1.3.0-rc.1+1.20.4-fabric8 сент. 2026 г.3Minecraft: 1.20.4fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.20.3)
1.3.0-rc.1+1.20.3-fabric8 сент. 2026 г.2Minecraft: 1.20.3fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.20.2)
1.3.0-rc.1+1.20.2-fabric8 сент. 2026 г.8Minecraft: 1.20.2fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.20.1)
1.3.0-rc.1+1.20.1-fabric8 сент. 2026 г.19Minecraft: 1.20.1fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-rc.1 (Fabric 1.20)
1.3.0-rc.1+1.20-fabric8 сент. 2026 г.4Minecraft: 1.20fabric
Что нового
First release candidate of the 1.3.0 line.
### Fixed
- Loads a mod that extends a Minecraft class the game deleted, instead of pointing it at whatever name looked closest. `RecordItem` was renamed onto `JukeboxSong`, which is a final record, so a music disc from an older mod failed at class load with `IncompatibleClassChangeError` naming a class the mod never mentioned. `EnchantedBookItem` was renamed onto `Items`, a static holder with no constructor, and failed at the `super(...)` call instead. Both inheritance edges now move onto a generated `Item` subclass, which grows a constructor for each `super(...)` call actually seen rather than working from a list of historical shapes that has to be kept correct by hand.
- Reports a superclass the host cannot accept, naming the mod class, why the class cannot be extended, and the class move that produced it. A class move is a flat rename with no idea which slot it lands in, so a destination that is fine as a type can be final, sealed, an interface, or gone. The check is positional and runs only on the name that reaches the `extends` slot, so a final class stays available everywhere it is used as a type.
- Keeps a call to `Lifecycle` as a class call. It was listed as a type that became an interface, so every call to it was rewritten into a form the JVM rejects with `IncompatibleClassChangeError: ... must be Methodref constant`. Lifecycle has never been an interface in any version Minecraft has shipped.
- Reads whether a call target is an interface from the host rather than from a fixed list. A list is right only for the versions it was written against, which is how the wrong entry above went unnoticed. The list survives as a fallback for an offline transform that cannot read the host, and each entry that changed kind now carries the version it changed in, so `DataResult` is treated as a class on 1.20 through 1.20.4 and an interface from 1.20.5 on.
- Transforms a NeoForge mod that declares no `minecraft` dependency. Plenty of mods leave it out because the `neoforge` range already pins the version, and Retromod read only the `minecraft` block, so the mod came back with no version and was skipped rather than transformed. It then reached the game untouched and failed on the first class Minecraft had moved, with nothing in the log to say Retromod had passed it over.
- Uses an installed copy of the target Minecraft version for offline transforms when `--mc-jar` is not given, looking under PrismLauncher and `.minecraft/versions`, and says which jar it used. The host checks above and the exact Mixin target analysis both depend on reading that jar, and both were unavailable by default. An explicit `--mc-jar` still decides it, and a jar named that way still has to be readable or the command fails.
- Renames a texture's animation metadata even when the pack does not override the texture itself. A pack can ship `fire_layer_0.png.mcmeta` without `fire_layer_0.png`, keeping the vanilla texture and replacing only its animation. The rename walked the `.png` files and carried each one's sidecar along, so a sidecar with no texture beside it kept the old name and stopped matching anything. Found by ttaute's resource-pack test from [pull request 238](https://github.com/Bownlux/Retromod/pull/238), included here with her [agreement](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5552837060). The texture tables from that work were already applied in snapshot.10, which is why she [closed it](https://github.com/Bownlux/Retromod/pull/238#issuecomment-5547553692); the test was the part still missing.
- Builds animation keyframes again. `Keyframe`'s constructors took the same joml concrete to interface widening as `Vec3` did, `Vector3f` becoming `Vector3fc`. Nothing changed at the source level, because `Vector3f` implements `Vector3fc`, so no mod author had reason to touch it; bytecode descriptors are exact, so every call failed with `NoSuchMethodError`. Entity animation mods build every keyframe this way, and linking two corpus mods against the real 26.2 and 26.3 jars found 4,760 such calls, all of them unresolved. Checking those two versions this way took unresolved calls into Minecraft from about a quarter to under five percent.
- Tracks Minecraft 26.3 through pre-release 2. All 230 class moves are re-checked against that client, three loot number provider moves are added for the split into int-valued and float-valued packages, and the resource-pack format for 26.3 is filled in.
- Accepts a pre-release jar for the version it belongs to. A 26.3 pre-release reports itself as `26.3-pre-2`, so `--target 26.3` was refused against the one jar that was right for it. The check now resolves both sides to their milestone, which is what the shim registry already does everywhere else.
- Corrects 40 intermediary mappings that pointed a Fabric mod at a class it never meant. Minecraft sometimes renames a class and then gives the old name to something new: the old `Cow` became `AbstractCow` and a new `Cow` appeared under it, `BushBlock` became `VegetationBlock`, and `SkipPacketException`, `BakedGlyph` and `DataComponentPredicate` all came back as interfaces over the class that replaced them. Each has two intermediary ids, and both were mapped to the newcomer, so a mod that meant the base linked against the leaf and behaved as if it were talking to a class it had never seen. Resolved by joining Fabric intermediary to the Mojang mappings for 1.21.11, the last version intermediary covers, at full coverage. Three class moves that collapsed two unrelated classes onto one name are corrected with them, including the DataFixer reference holders that were pointed at the vanilla `Items` and `Blocks` registries.
- Corrects eight class moves that named the wrong destination, after auditing all 914 rows against the 26.1.2 and 26.2 jars. Four now point at the class that really succeeded the old one: a compiled shader program, a rendered buffer, a chat report, and a horse coat variant, each verified by comparing declared members rather than matching names. Four had no successor at all and are removed, because naming the nearest surviving class turned a missing class into a linkage error further from the cause: a translatable component onto a payload that is not a component, a holder of six int constants onto an enum, and a callback interface onto a class with no public constructor. One more move the table never had is added, for the behavior package's own weighted list.
- Hardens the handling of malformed mod metadata and bytecode so a damaged or hand-edited jar is refused rather than allowed to produce a broken helper class or an unreasonably long transform. Details are held until 1.3.0 is stable.
- Computes a generated Mixin bridge's stack size instead of counting it by hand. One bridge declared six slots for a body that uses seven, which is a `VerifyError` at class load and reaches the user as the mod failing to load with nothing naming the bridge. Found by running a dataflow analysis over real transformed mods.
- Corrects three SRG rows the table's own header had flagged as suspect and shipped anyway: one method paired with an unrelated renderer name instead of the builder method it really is, one field name misspelled so it could never match, and one pairing that no available mapping data supports, which is removed rather than guessed at.
- Loads the two SRG name tables in the right order. They share 5,458 keys, and the weaker of the two was overriding the stronger one on every collision, because a comment asserted the key patterns never collide. A 1.12.2 mod's member was rewritten to the name from its own era rather than the host's, so `isRemoved` became `isInvalid`, `id` became `entityId`, and `maxLength` became `maxStringLength`, none of which exist on a modern host. Two of the colliding names resolve to real members on unrelated classes, which fails silently rather than loudly. No entry is lost: the 1.12.2 table is still the only source for the ids the main table does not carry.
- Covers the removed item bases in all three test mods, so the repair is exercised by a real mod build rather than only by unit tests. The music disc base is absent from the NeoForge mod because Minecraft removed it in 1.21, which that mod targets.
- Drops two class moves that paired unrelated classes by their simple name: a horse coat variant onto a block model record, and a villager schedule keyframe onto an animation keyframe. Both destinations are final records in unrelated package trees, so the rename could not be right in any position. An audit of all 914 rows against the 26.1.2 and 26.2 jars found no others of this kind.
- Carries a nested chunk-rendering class through the 26.2 rename that the 26.1 table stopped short of, so a mod translated across both jumps no longer keeps a name 26.2 does not have.
The generated base restores linkage, not behavior. Arguments the modern base does not take are dropped, so a converted music disc keeps its name, texture, recipe, and creative tab, and does not play in a jukebox. Playback now comes from the `jukebox_playable` component and a `jukebox_song` datapack entry that an older mod does not ship.
`AgeableListModel` was deleted in the same way and has no surviving base that accepts its constructors, so a mod that extends it still needs a real model port. It is named in the log now instead of producing an inheritance error.
Both the superclass check and the interface-kind check need to read the target Minecraft jar. Offline commands now find an installed copy on their own, and `--mc-jar` still names one explicitly. With neither available they fall back to their tables.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 26.2)
1.3.0-snapshot.10+26.2-neoforge30 авг. 2026 г.100Minecraft: 26.2neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 26.1.2)
1.3.0-snapshot.10+26.1.2-nf30 авг. 2026 г.44Minecraft: 26.1.2neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 26.1.1)
1.3.0-snapshot.10+26.1.1-nf30 авг. 2026 г.6Minecraft: 26.1.1neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 26.1)
1.3.0-snapshot.10+26.1-neoforge30 авг. 2026 г.3Minecraft: 26.1neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.21.11)
1.3.0-snapshot.10+1.21.11-nf30 авг. 2026 г.16Minecraft: 1.21.11neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.21.10)
1.3.0-snapshot.10+1.21.10-nf30 авг. 2026 г.7Minecraft: 1.21.10neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.21.9)
1.3.0-snapshot.10+1.21.9-nf30 авг. 2026 г.14Minecraft: 1.21.9neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.21.8)
1.3.0-snapshot.10+1.21.8-nf30 авг. 2026 г.10Minecraft: 1.21.8neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.21.7)
1.3.0-snapshot.10+1.21.7-nf30 авг. 2026 г.5Minecraft: 1.21.7neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.21.6)
1.3.0-snapshot.10+1.21.6-nf30 авг. 2026 г.4Minecraft: 1.21.6neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.21.5)
1.3.0-snapshot.10+1.21.5-nf30 авг. 2026 г.2Minecraft: 1.21.5neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.21.4)
1.3.0-snapshot.10+1.21.4-nf30 авг. 2026 г.8Minecraft: 1.21.4neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.21.3)
1.3.0-snapshot.10+1.21.3-nf30 авг. 2026 г.1Minecraft: 1.21.3neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.21.2)
1.3.0-snapshot.10+1.21.2-nf30 авг. 2026 г.1Minecraft: 1.21.2neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.21.1)
1.3.0-snapshot.10+1.21.1-nf30 авг. 2026 г.212Minecraft: 1.21.1neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.21)
1.3.0-snapshot.10+1.21-neoforge30 авг. 2026 г.1Minecraft: 1.21neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.20.6)
1.3.0-snapshot.10+1.20.6-nf30 авг. 2026 г.4Minecraft: 1.20.6neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.20.5)
1.3.0-snapshot.10+1.20.5-nf30 авг. 2026 г.1Minecraft: 1.20.5neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.20.4)
1.3.0-snapshot.10+1.20.4-nf30 авг. 2026 г.2Minecraft: 1.20.4neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.20.3)
1.3.0-snapshot.10+1.20.3-nf30 авг. 2026 г.5Minecraft: 1.20.3neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.20.2)
1.3.0-snapshot.10+1.20.2-nf30 авг. 2026 г.1Minecraft: 1.20.2neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (NeoForge 1.20.1)
1.3.0-snapshot.10+1.20.1-nf30 авг. 2026 г.8Minecraft: 1.20.1neoforge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 26.2)
1.3.0-snapshot.10+26.2-forge30 авг. 2026 г.23Minecraft: 26.2forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 26.1.2)
1.3.0-snapshot.10+26.1.2-forge30 авг. 2026 г.14Minecraft: 26.1.2forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 26.1.1)
1.3.0-snapshot.10+26.1.1-forge30 авг. 2026 г.0Minecraft: 26.1.1forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 26.1)
1.3.0-snapshot.10+26.1-forge30 авг. 2026 г.5Minecraft: 26.1forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.21.11)
1.3.0-snapshot.10+1.21.11-forge30 авг. 2026 г.9Minecraft: 1.21.11forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.21.10)
1.3.0-snapshot.10+1.21.10-forge30 авг. 2026 г.0Minecraft: 1.21.10forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.21.9)
1.3.0-snapshot.10+1.21.9-forge30 авг. 2026 г.0Minecraft: 1.21.9forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.21.8)
1.3.0-snapshot.10+1.21.8-forge30 авг. 2026 г.0Minecraft: 1.21.8forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.21.7)
1.3.0-snapshot.10+1.21.7-forge30 авг. 2026 г.1Minecraft: 1.21.7forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.21.6)
1.3.0-snapshot.10+1.21.6-forge30 авг. 2026 г.0Minecraft: 1.21.6forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.21.5)
1.3.0-snapshot.10+1.21.5-forge30 авг. 2026 г.4Minecraft: 1.21.5forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.21.4)
1.3.0-snapshot.10+1.21.4-forge30 авг. 2026 г.0Minecraft: 1.21.4forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.21.3)
1.3.0-snapshot.10+1.21.3-forge30 авг. 2026 г.1Minecraft: 1.21.3forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.21.2)
1.3.0-snapshot.10+1.21.2-forge30 авг. 2026 г.0Minecraft: 1.21.2forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.21.1)
1.3.0-snapshot.10+1.21.1-forge30 авг. 2026 г.18Minecraft: 1.21.1forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.21)
1.3.0-snapshot.10+1.21-forge30 авг. 2026 г.1Minecraft: 1.21forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.20.6)
1.3.0-snapshot.10+1.20.6-forge30 авг. 2026 г.1Minecraft: 1.20.6forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.20.5)
1.3.0-snapshot.10+1.20.5-forge30 авг. 2026 г.1Minecraft: 1.20.5forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.20.4)
1.3.0-snapshot.10+1.20.4-forge30 авг. 2026 г.4Minecraft: 1.20.4forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.20.3)
1.3.0-snapshot.10+1.20.3-forge30 авг. 2026 г.2Minecraft: 1.20.3forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.20.2)
1.3.0-snapshot.10+1.20.2-forge30 авг. 2026 г.1Minecraft: 1.20.2forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.20.1)
1.3.0-snapshot.10+1.20.1-forge30 авг. 2026 г.101Minecraft: 1.20.1forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Forge 1.20)
1.3.0-snapshot.10+1.20-forge30 авг. 2026 г.1Minecraft: 1.20forge
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 26.2)
1.3.0-snapshot.10+26.2-fabric30 авг. 2026 г.353Minecraft: 26.2fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 26.1.2)
1.3.0-snapshot.10+26.1.2-fabric30 авг. 2026 г.82Minecraft: 26.1.2fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 26.1.1)
1.3.0-snapshot.10+26.1.1-fabric30 авг. 2026 г.1Minecraft: 26.1.1fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 26.1)
1.3.0-snapshot.10+26.1-fabric30 авг. 2026 г.8Minecraft: 26.1fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.21.11)
1.3.0-snapshot.10+1.21.11-fabric30 авг. 2026 г.113Minecraft: 1.21.11fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.21.10)
1.3.0-snapshot.10+1.21.10-fabric30 авг. 2026 г.11Minecraft: 1.21.10fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.21.9)
1.3.0-snapshot.10+1.21.9-fabric30 авг. 2026 г.7Minecraft: 1.21.9fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.21.8)
1.3.0-snapshot.10+1.21.8-fabric30 авг. 2026 г.7Minecraft: 1.21.8fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.21.7)
1.3.0-snapshot.10+1.21.7-fabric30 авг. 2026 г.1Minecraft: 1.21.7fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.21.6)
1.3.0-snapshot.10+1.21.6-fabric30 авг. 2026 г.3Minecraft: 1.21.6fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.21.5)
1.3.0-snapshot.10+1.21.5-fabric30 авг. 2026 г.2Minecraft: 1.21.5fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.21.4)
1.3.0-snapshot.10+1.21.4-fabric30 авг. 2026 г.4Minecraft: 1.21.4fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.21.3)
1.3.0-snapshot.10+1.21.3-fabric30 авг. 2026 г.0Minecraft: 1.21.3fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.21.2)
1.3.0-snapshot.10+1.21.2-fabric30 авг. 2026 г.1Minecraft: 1.21.2fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.21.1)
1.3.0-snapshot.10+1.21.1-fabric30 авг. 2026 г.59Minecraft: 1.21.1fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.21)
1.3.0-snapshot.10+1.21-fabric30 авг. 2026 г.6Minecraft: 1.21fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.20.6)
1.3.0-snapshot.10+1.20.6-fabric30 авг. 2026 г.1Minecraft: 1.20.6fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.20.5)
1.3.0-snapshot.10+1.20.5-fabric30 авг. 2026 г.3Minecraft: 1.20.5fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.20.4)
1.3.0-snapshot.10+1.20.4-fabric30 авг. 2026 г.3Minecraft: 1.20.4fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.20.3)
1.3.0-snapshot.10+1.20.3-fabric30 авг. 2026 г.2Minecraft: 1.20.3fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.20.2)
1.3.0-snapshot.10+1.20.2-fabric30 авг. 2026 г.2Minecraft: 1.20.2fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.20.1)
1.3.0-snapshot.10+1.20.1-fabric30 авг. 2026 г.27Minecraft: 1.20.1fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.10 (Fabric 1.20)
1.3.0-snapshot.10+1.20-fabric30 авг. 2026 г.2Minecraft: 1.20fabric
Что нового
Tenth snapshot of the 1.3.0 line.
### Added
- Adds an offline `resourcepack` CLI command for updating one zip or unpacked pack without launching Minecraft. It keeps the source unchanged and refuses existing or nested output paths.
- Validates texture mapping tables for safe one-to-one basenames, no-op entries, block and item isolation, and animation sidecar handling.
- Moves 33 additional pre-1.13 entity textures to their verified Flattening paths, including boats, beds, illagers, end crystals, snow golems, and llamas.
- Chains 193 verified texture moves across 14 resource-pack format boundaries, including equipment, armor trims, GUI slots, entity reorganizations, and 26.2 pillar names.
- Repairs a MixinExtras local variable capture when the method it hooks gained a parameter, instead of refusing the handler outright. A capture that names a slot moves with the variable it selected, and a capture that selects by ordinal, by name, or by type is carried through unchanged.
A capture of the same type as the added parameter is still refused, because it gains a second candidate and nothing in the bytecode says which one the author meant. A capture annotated before the `CallbackInfo` is refused as well.
- Declines a handler repair when the handler's body names a Minecraft field or method the host no longer declares. Repairing a signature is what makes Mixin apply a handler, so repairing one whose body is already broken replaces a dormant feature with a crash at whatever moment that feature runs. A member whose name survives is left to the repair engines, so a method that only gained a parameter still translates as before.
### Added
- Adds Minecraft 26.3 support for Fabric, NeoForge, and Forge. 26.3 repackaged its rendering library, moving `com/mojang/blaze3d` to `com/mojang/renderpearl`, and renamed a number of vanilla classes, several only to correct capitalization such as `EnderMan` to `Enderman`. All 196 moves are checked against the 26.2 and 26.3 jars: the old name must be gone, the new name must exist, and the pairing must be proven rather than guessed from name similarity.
- Recognizes Minecraft's own snapshot spelling. `26.3-snapshot-10` and the pre-release and candidate forms all resolve to 26.3, and the equivalent hyphenated 26.2 spellings now resolve too.
- Keeps Retromod's own embedded helper classes on the target's class names. A helper is compiled against one Minecraft version, so its references age like a mod's do, and one that still named `com/mojang/blaze3d/PrimitiveTopology` failed to link on 26.3 even when the mod around it was translated correctly. All three embedding paths follow the moves now.
- Follows Minecraft 26.3 adding a leading `ServerLevel` to `LivingEntity.getVisibilityPercent`, and only on 26.3 and newer. Signature repairs can now carry the version they start from, because the table sat behind a single 1.21.5 gate and a later change applied on an older host would have broken a handler that was already correct.
- Follows GeckoLib's move from `software.bernie.geckolib` to `com.geckolib`. GeckoLib 5 renamed the package and reorganised the library in one release, so a mod built against 4.x fails on its first animated item or entity with `NoClassDefFoundError` naming a class that is simply gone, even though the host has GeckoLib installed and working. The table is derived from the 4.8.4 and 5.5.2 jars and is held back below Minecraft 1.21.5, since that is where GeckoLib 5 begins and rewriting earlier would point a mod at packages its host does not ship.
- Reaches FML state that stopped being static on NeoForge. A mod built against the older loader calls `FMLLoader.getDist()` and `getLoadingModList()` directly, and current NeoForge keeps both behind `FMLLoader.getCurrent()`, so the mod dies on its first call with `IncompatibleClassChangeError` naming a method that plainly exists. The helper those redirects pointed at had never been written, so the `getLoadingModList` bridge had been silently inert as well.
- Derives the Flattening texture renames from the vanilla jars instead of a hand-written list, taking the table from 9 entries to 413. A pack used to convert mostly right and quietly keep whatever the list had not reached yet. Two independent methods produce the pairs, identical PNG content across the 1.13 boundary and the texture a vanilla model names, and they agreed on 250 of the 251 pairs both produced. Every row is checked against the current jar, so a rename lands on a texture the game still ships, and five textures renamed a second time after 1.13 are chained to their current name.
- Renames item textures as well as block textures. The two tables are kept apart because a 1.12.2 pack ships both `blocks/brick.png` and `items/brick.png` and only the block one became `bricks`.
- Extends the later texture migrations the same way, from 193 rows to 219, derived by comparing adjacent versions at every pack-format boundary from 1.13 to 26.2. Adjacent versions are compared rather than the endpoints because a rename and a redraw look the same by name and rarely happen in the same version. The derived pass and the hand-checked rows agreed on 187 of the rows they both produced.
- Adds the 24 sign textures 26.2 moved out of `entity/signs/` into `block/`. Content matching could not pair them because 26.2 redrew them as it moved them, so they were mapped by name with both ends checked against the jars.
- Repoints a converted pack's own models at the textures they moved to. Moving a texture was only half the job: a pack shipping its own models still named the old path, so Minecraft resolved nothing and drew the missing-texture checkerboard. The pack looked converted and was quietly broken. Only the `textures` block of a model is rewritten, since a `parent` and a blockstate name models rather than textures.
- Converts every namespace in a pack, not only `assets/minecraft`. A pack that also skins mods keeps those textures under `assets/<modid>`, and they followed the same 1.13 layout change, so the vanilla half converted and the modded half silently did not.
- Keeps the current file when a pack ships a texture on both the old and the new path, instead of refusing the whole pack. A pack carrying both layouts is saying which one it means.
The rendering repackage is partial: `VertexConsumer`, `BufferBuilder`, `ByteBufferBuilder`, and `PoseStack` stayed where they were, so only the classes that actually moved are redirected. A class move restores linkage, not behavior, and the rendering API changed alongside the repackage.
The pack work above came out of a conversation with ttaute, whose Flattening-era texture tables are in review. Her observation that this area had barely changed since its first prototype commit is what prompted the look, and the gaps it turned up are the ones fixed here. Her hand-checked entries are applied on top of the derived table and win any disagreement, and her own test for the block and item `brick` split passes against it unchanged. Her multi-step migrations are kept as steps rather than collapsed: a pack sitting at an intermediate name still needs the rest of the journey, and a test now holds that shape.
26.3 also removes rather than moves two groups, so neither is covered: most of the worldgen configuration system, including `FeatureConfiguration` and its subclasses, `ConfiguredFeature`, and `SurfaceRules`; and the last of the hardcoded item classes, `AxeItem`, `HoeItem`, `ShovelItem`, `BedItem`, and `SignItem`. Mods built on either need a real port, and both are listed in Mods That Can't Be Translated.
OpenGL was expected to be removed in 26.3 and has not been. 26.3-snapshot-10 still ships `PreferredGraphicsApi.OPENGL`, so Retromod keeps selecting OpenGL for translated mods on that host.
Every Mixin blocklist entry was re-checked against the 26.2 jar and none can be retired yet. Deeper and Darker's painting handler looks like the easiest, because its target only gained a leading `ServerLevel`. Its body reads `Painting.VARIANT_CODEC`, which 26.2 removed entirely, so repairing the signature would replace a dormant feature with a crash when a painting breaks. Retiring an entry needs the handler's body checked as well as its signature.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 26.2)
1.3.0-snapshot.9+26.2-neoforge23 авг. 2026 г.61Minecraft: 26.2neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 26.1.2)
1.3.0-snapshot.9+26.1.2-neoforge23 авг. 2026 г.28Minecraft: 26.1.2neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 26.1.1)
1.3.0-snapshot.9+26.1.1-neoforge23 авг. 2026 г.4Minecraft: 26.1.1neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 26.1)
1.3.0-snapshot.9+26.1-neoforge23 авг. 2026 г.5Minecraft: 26.1neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.21.11)
1.3.0-snapshot.9+1.21.11-nf23 авг. 2026 г.18Minecraft: 1.21.11neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.21.10)
1.3.0-snapshot.9+1.21.10-nf23 авг. 2026 г.2Minecraft: 1.21.10neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.21.9)
1.3.0-snapshot.9+1.21.9-neoforge23 авг. 2026 г.2Minecraft: 1.21.9neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.21.8)
1.3.0-snapshot.9+1.21.8-neoforge23 авг. 2026 г.4Minecraft: 1.21.8neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.21.7)
1.3.0-snapshot.9+1.21.7-neoforge23 авг. 2026 г.2Minecraft: 1.21.7neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.21.6)
1.3.0-snapshot.9+1.21.6-neoforge23 авг. 2026 г.3Minecraft: 1.21.6neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.21.5)
1.3.0-snapshot.9+1.21.5-neoforge23 авг. 2026 г.1Minecraft: 1.21.5neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.21.4)
1.3.0-snapshot.9+1.21.4-neoforge23 авг. 2026 г.3Minecraft: 1.21.4neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.21.3)
1.3.0-snapshot.9+1.21.3-neoforge23 авг. 2026 г.2Minecraft: 1.21.3neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.21.2)
1.3.0-snapshot.9+1.21.2-neoforge23 авг. 2026 г.2Minecraft: 1.21.2neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.21.1)
1.3.0-snapshot.9+1.21.1-neoforge23 авг. 2026 г.176Minecraft: 1.21.1neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.21)
1.3.0-snapshot.9+1.21-neoforge23 авг. 2026 г.9Minecraft: 1.21neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.20.6)
1.3.0-snapshot.9+1.20.6-neoforge23 авг. 2026 г.2Minecraft: 1.20.6neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.20.5)
1.3.0-snapshot.9+1.20.5-neoforge23 авг. 2026 г.0Minecraft: 1.20.5neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.20.4)
1.3.0-snapshot.9+1.20.4-neoforge23 авг. 2026 г.2Minecraft: 1.20.4neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.20.3)
1.3.0-snapshot.9+1.20.3-neoforge23 авг. 2026 г.2Minecraft: 1.20.3neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.20.2)
1.3.0-snapshot.9+1.20.2-neoforge23 авг. 2026 г.0Minecraft: 1.20.2neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (NeoForge 1.20.1)
1.3.0-snapshot.9+1.20.1-neoforge23 авг. 2026 г.11Minecraft: 1.20.1neoforge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 26.2)
1.3.0-snapshot.9+26.2-forge23 авг. 2026 г.39Minecraft: 26.2forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 26.1.2)
1.3.0-snapshot.9+26.1.2-forge23 авг. 2026 г.11Minecraft: 26.1.2forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 26.1.1)
1.3.0-snapshot.9+26.1.1-forge23 авг. 2026 г.1Minecraft: 26.1.1forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 26.1)
1.3.0-snapshot.9+26.1-forge23 авг. 2026 г.0Minecraft: 26.1forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.21.11)
1.3.0-snapshot.9+1.21.11-forge23 авг. 2026 г.7Minecraft: 1.21.11forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.21.10)
1.3.0-snapshot.9+1.21.10-forge23 авг. 2026 г.2Minecraft: 1.21.10forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.21.9)
1.3.0-snapshot.9+1.21.9-forge23 авг. 2026 г.3Minecraft: 1.21.9forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.21.8)
1.3.0-snapshot.9+1.21.8-forge23 авг. 2026 г.1Minecraft: 1.21.8forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.21.7)
1.3.0-snapshot.9+1.21.7-forge23 авг. 2026 г.0Minecraft: 1.21.7forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.21.6)
1.3.0-snapshot.9+1.21.6-forge23 авг. 2026 г.3Minecraft: 1.21.6forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.21.5)
1.3.0-snapshot.9+1.21.5-forge23 авг. 2026 г.2Minecraft: 1.21.5forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.21.4)
1.3.0-snapshot.9+1.21.4-forge23 авг. 2026 г.1Minecraft: 1.21.4forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.21.3)
1.3.0-snapshot.9+1.21.3-forge23 авг. 2026 г.1Minecraft: 1.21.3forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.21.2)
1.3.0-snapshot.9+1.21.2-forge23 авг. 2026 г.2Minecraft: 1.21.2forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.21.1)
1.3.0-snapshot.9+1.21.1-forge23 авг. 2026 г.16Minecraft: 1.21.1forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.21)
1.3.0-snapshot.9+1.21-forge23 авг. 2026 г.3Minecraft: 1.21forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.20.6)
1.3.0-snapshot.9+1.20.6-forge23 авг. 2026 г.2Minecraft: 1.20.6forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.20.5)
1.3.0-snapshot.9+1.20.5-forge23 авг. 2026 г.1Minecraft: 1.20.5forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.20.4)
1.3.0-snapshot.9+1.20.4-forge23 авг. 2026 г.0Minecraft: 1.20.4forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.20.3)
1.3.0-snapshot.9+1.20.3-forge23 авг. 2026 г.0Minecraft: 1.20.3forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.20.2)
1.3.0-snapshot.9+1.20.2-forge23 авг. 2026 г.0Minecraft: 1.20.2forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.20.1)
1.3.0-snapshot.9+1.20.1-forge23 авг. 2026 г.135Minecraft: 1.20.1forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Forge 1.20)
1.3.0-snapshot.9+1.20-forge23 авг. 2026 г.0Minecraft: 1.20forge
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 26.2)
1.3.0-snapshot.9+26.2-fabric23 авг. 2026 г.343Minecraft: 26.2fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 26.1.2)
1.3.0-snapshot.9+26.1.2-fabric23 авг. 2026 г.67Minecraft: 26.1.2fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 26.1.1)
1.3.0-snapshot.9+26.1.1-fabric23 авг. 2026 г.2Minecraft: 26.1.1fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 26.1)
1.3.0-snapshot.9+26.1-fabric23 авг. 2026 г.8Minecraft: 26.1fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.21.11)
1.3.0-snapshot.9+1.21.11-fabric23 авг. 2026 г.121Minecraft: 1.21.11fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.21.10)
1.3.0-snapshot.9+1.21.10-fabric23 авг. 2026 г.17Minecraft: 1.21.10fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.21.9)
1.3.0-snapshot.9+1.21.9-fabric23 авг. 2026 г.1Minecraft: 1.21.9fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.21.8)
1.3.0-snapshot.9+1.21.8-fabric23 авг. 2026 г.7Minecraft: 1.21.8fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.21.7)
1.3.0-snapshot.9+1.21.7-fabric23 авг. 2026 г.3Minecraft: 1.21.7fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.21.6)
1.3.0-snapshot.9+1.21.6-fabric23 авг. 2026 г.2Minecraft: 1.21.6fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.21.5)
1.3.0-snapshot.9+1.21.5-fabric23 авг. 2026 г.2Minecraft: 1.21.5fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.21.4)
1.3.0-snapshot.9+1.21.4-fabric23 авг. 2026 г.28Minecraft: 1.21.4fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.21.3)
1.3.0-snapshot.9+1.21.3-fabric23 авг. 2026 г.1Minecraft: 1.21.3fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.21.2)
1.3.0-snapshot.9+1.21.2-fabric23 авг. 2026 г.3Minecraft: 1.21.2fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.21.1)
1.3.0-snapshot.9+1.21.1-fabric23 авг. 2026 г.62Minecraft: 1.21.1fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.21)
1.3.0-snapshot.9+1.21-fabric23 авг. 2026 г.2Minecraft: 1.21fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.20.6)
1.3.0-snapshot.9+1.20.6-fabric23 авг. 2026 г.1Minecraft: 1.20.6fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.20.5)
1.3.0-snapshot.9+1.20.5-fabric23 авг. 2026 г.1Minecraft: 1.20.5fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.20.4)
1.3.0-snapshot.9+1.20.4-fabric23 авг. 2026 г.7Minecraft: 1.20.4fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.20.3)
1.3.0-snapshot.9+1.20.3-fabric23 авг. 2026 г.1Minecraft: 1.20.3fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.20.2)
1.3.0-snapshot.9+1.20.2-fabric23 авг. 2026 г.3Minecraft: 1.20.2fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.20.1)
1.3.0-snapshot.9+1.20.1-fabric23 авг. 2026 г.17Minecraft: 1.20.1fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.9 (Fabric 1.20)
1.3.0-snapshot.9+1.20-fabric23 авг. 2026 г.1Minecraft: 1.20fabric
Что нового
Ninth snapshot of the 1.3.0 line.
### Added
- Moves a legacy accessor-only Mixin when one target, an exact owner-scoped field redirect, and an unchanged descriptor prove the new owner. It follows Mixin's implicit-name rules and adds `@Mutable` only when the shim marks that destination as mutable.
### Fixed
- Repairs legacy block random-tick accessors after the field moved from `Block` to `BlockBehaviour` and became final. Calls through a proven setter now refresh every cached block state, so the new value reaches random-tick scheduling.
- Updates 48 same-contract `GameRules` constants after Fabric intermediary names expand to retired `RULE_*` spellings on 26.1 and newer. Retromod leaves the changed fire-tick rule and three inverted boolean rules untouched because a field rename cannot preserve their values.
- Transforms explicitly selected `aot` and `batch` inputs when their metadata omits the source Minecraft version. Retromod applies only matching-loader shims available on the selected host, while automatic scans still leave unknown-version native mods alone.
- Prevents `<target`, `<=target`, and `>target` Fabric constraints from being mistaken for native target declarations. Exact target predicates and matching wildcards still pass through unchanged.
- Uses the detected Minecraft host version for transforms started from the in-game compatibility screen. A 26.2 client no longer prepares those mods as 26.1 output.
- Takes an immutable snapshot of a mod's classes before parallel Fabric, Forge, or NeoForge transforms begin. Frame rebuilding and post-remap adapters no longer risk reading a class while another worker replaces it.
- Preserves the implementation namespace of bundled MixinExtras libraries while still translating legacy calls from mod code. Nested-jar transforms no longer disable MixinExtras initialization and break unrelated Fabric API mixins that use `@Local` parameters.
- Applies matching common API bridges during known-source CLI and AOT transforms. API release numbers stay outside Minecraft version-chain searches.
- Selects library API repairs outside the Minecraft graph while still gating host-versioned API repairs. Explicit same-version CLI and AOT transforms can apply those providers.
- Updates staged resource and data packs through Minecraft 26.2, including current minor pack formats. Data packs run compatible 26.x content migration when needed, while legacy resource packs run item-definition and content migration only during an actual format upgrade.
- Processes staged resource and data packs on Fabric, Quilt, Forge, and NeoForge instead of limiting pack work to Fabric startup.
- Reads the Minecraft dependency from `quilt.mod.json` and keeps it authoritative when a jar also contains Fabric metadata. The shared Fabric artifact carries both metadata files and uses entrypoint contracts Quilt Loader can invoke, so Retromod does not publish a separate Quilt artifact.
- Keeps the Windows and Unix release builders on the same version, Minecraft matrix, integrity checks, artifact counts, and checksum output. `build.sh` and `build.bat` now delegate to the canonical builders.
### Changed
- Updates JUnit Jupiter from 5.10.1 to 6.1.3 and the release publisher's `charset-normalizer` from 3.4.9 to 3.5.1.
Accessor owner moves still refuse multiple Mixin targets, mixed-purpose interfaces, unmatched companion accessors, conflicting destination fields, method-shaped redirects, and descriptor changes. Unknown-source fallback has no exact starting version, so a precise metadata version remains safer. Pack migration refuses unknown targets, malformed metadata, newer-only input, overlays, and conflicting paths. Those sources remain staged. The removed fire-tick boolean and inverted game rules need value-aware adapters.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 26.2)
1.3.0-snapshot.8+26.2-neoforge18 авг. 2026 г.59Minecraft: 26.2neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 26.1.2)
1.3.0-snapshot.8+26.1.2-neoforge18 авг. 2026 г.27Minecraft: 26.1.2neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 26.1.1)
1.3.0-snapshot.8+26.1.1-neoforge18 авг. 2026 г.1Minecraft: 26.1.1neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 26.1)
1.3.0-snapshot.8+26.1-neoforge18 авг. 2026 г.4Minecraft: 26.1neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.21.11)
1.3.0-snapshot.8+1.21.11-nf18 авг. 2026 г.12Minecraft: 1.21.11neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.21.10)
1.3.0-snapshot.8+1.21.10-nf18 авг. 2026 г.3Minecraft: 1.21.10neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.21.9)
1.3.0-snapshot.8+1.21.9-neoforge18 авг. 2026 г.5Minecraft: 1.21.9neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.21.8)
1.3.0-snapshot.8+1.21.8-neoforge18 авг. 2026 г.7Minecraft: 1.21.8neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.21.7)
1.3.0-snapshot.8+1.21.7-neoforge18 авг. 2026 г.2Minecraft: 1.21.7neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.21.6)
1.3.0-snapshot.8+1.21.6-neoforge18 авг. 2026 г.5Minecraft: 1.21.6neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.21.5)
1.3.0-snapshot.8+1.21.5-neoforge18 авг. 2026 г.1Minecraft: 1.21.5neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.21.4)
1.3.0-snapshot.8+1.21.4-neoforge18 авг. 2026 г.5Minecraft: 1.21.4neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.21.3)
1.3.0-snapshot.8+1.21.3-neoforge18 авг. 2026 г.3Minecraft: 1.21.3neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.21.2)
1.3.0-snapshot.8+1.21.2-neoforge18 авг. 2026 г.1Minecraft: 1.21.2neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.21.1)
1.3.0-snapshot.8+1.21.1-neoforge18 авг. 2026 г.134Minecraft: 1.21.1neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.21)
1.3.0-snapshot.8+1.21-neoforge18 авг. 2026 г.2Minecraft: 1.21neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.20.6)
1.3.0-snapshot.8+1.20.6-neoforge18 авг. 2026 г.1Minecraft: 1.20.6neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.20.5)
1.3.0-snapshot.8+1.20.5-neoforge18 авг. 2026 г.2Minecraft: 1.20.5neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.20.4)
1.3.0-snapshot.8+1.20.4-neoforge18 авг. 2026 г.3Minecraft: 1.20.4neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.20.3)
1.3.0-snapshot.8+1.20.3-neoforge18 авг. 2026 г.2Minecraft: 1.20.3neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.20.2)
1.3.0-snapshot.8+1.20.2-neoforge18 авг. 2026 г.3Minecraft: 1.20.2neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (NeoForge 1.20.1)
1.3.0-snapshot.8+1.20.1-neoforge18 авг. 2026 г.22Minecraft: 1.20.1neoforge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 26.2)
1.3.0-snapshot.8+26.2-forge18 авг. 2026 г.37Minecraft: 26.2forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 26.1.2)
1.3.0-snapshot.8+26.1.2-forge18 авг. 2026 г.14Minecraft: 26.1.2forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 26.1.1)
1.3.0-snapshot.8+26.1.1-forge18 авг. 2026 г.2Minecraft: 26.1.1forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 26.1)
1.3.0-snapshot.8+26.1-forge18 авг. 2026 г.1Minecraft: 26.1forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.21.11)
1.3.0-snapshot.8+1.21.11-forge18 авг. 2026 г.11Minecraft: 1.21.11forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.21.10)
1.3.0-snapshot.8+1.21.10-forge18 авг. 2026 г.2Minecraft: 1.21.10forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.21.9)
1.3.0-snapshot.8+1.21.9-forge18 авг. 2026 г.4Minecraft: 1.21.9forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.21.8)
1.3.0-snapshot.8+1.21.8-forge18 авг. 2026 г.2Minecraft: 1.21.8forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.21.7)
1.3.0-snapshot.8+1.21.7-forge18 авг. 2026 г.2Minecraft: 1.21.7forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.21.6)
1.3.0-snapshot.8+1.21.6-forge18 авг. 2026 г.5Minecraft: 1.21.6forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.21.5)
1.3.0-snapshot.8+1.21.5-forge18 авг. 2026 г.1Minecraft: 1.21.5forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.21.4)
1.3.0-snapshot.8+1.21.4-forge18 авг. 2026 г.0Minecraft: 1.21.4forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.21.3)
1.3.0-snapshot.8+1.21.3-forge18 авг. 2026 г.1Minecraft: 1.21.3forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.21.2)
1.3.0-snapshot.8+1.21.2-forge18 авг. 2026 г.1Minecraft: 1.21.2forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.21.1)
1.3.0-snapshot.8+1.21.1-forge18 авг. 2026 г.15Minecraft: 1.21.1forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.21)
1.3.0-snapshot.8+1.21-forge18 авг. 2026 г.2Minecraft: 1.21forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.20.6)
1.3.0-snapshot.8+1.20.6-forge18 авг. 2026 г.2Minecraft: 1.20.6forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.20.5)
1.3.0-snapshot.8+1.20.5-forge18 авг. 2026 г.4Minecraft: 1.20.5forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.20.4)
1.3.0-snapshot.8+1.20.4-forge18 авг. 2026 г.0Minecraft: 1.20.4forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.20.3)
1.3.0-snapshot.8+1.20.3-forge18 авг. 2026 г.0Minecraft: 1.20.3forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.20.2)
1.3.0-snapshot.8+1.20.2-forge18 авг. 2026 г.0Minecraft: 1.20.2forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.20.1)
1.3.0-snapshot.8+1.20.1-forge18 авг. 2026 г.107Minecraft: 1.20.1forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Forge 1.20)
1.3.0-snapshot.8+1.20-forge18 авг. 2026 г.2Minecraft: 1.20forge
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 26.2)
1.3.0-snapshot.8+26.2-fabric18 авг. 2026 г.267Minecraft: 26.2fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 26.1.2)
1.3.0-snapshot.8+26.1.2-fabric18 авг. 2026 г.54Minecraft: 26.1.2fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 26.1.1)
1.3.0-snapshot.8+26.1.1-fabric18 авг. 2026 г.2Minecraft: 26.1.1fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 26.1)
1.3.0-snapshot.8+26.1-fabric18 авг. 2026 г.4Minecraft: 26.1fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.21.11)
1.3.0-snapshot.8+1.21.11-fabric18 авг. 2026 г.65Minecraft: 1.21.11fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.21.10)
1.3.0-snapshot.8+1.21.10-fabric18 авг. 2026 г.2Minecraft: 1.21.10fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.21.9)
1.3.0-snapshot.8+1.21.9-fabric18 авг. 2026 г.3Minecraft: 1.21.9fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.21.8)
1.3.0-snapshot.8+1.21.8-fabric18 авг. 2026 г.14Minecraft: 1.21.8fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.21.7)
1.3.0-snapshot.8+1.21.7-fabric18 авг. 2026 г.2Minecraft: 1.21.7fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.21.6)
1.3.0-snapshot.8+1.21.6-fabric18 авг. 2026 г.1Minecraft: 1.21.6fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.21.5)
1.3.0-snapshot.8+1.21.5-fabric18 авг. 2026 г.1Minecraft: 1.21.5fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.21.4)
1.3.0-snapshot.8+1.21.4-fabric18 авг. 2026 г.4Minecraft: 1.21.4fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.21.3)
1.3.0-snapshot.8+1.21.3-fabric18 авг. 2026 г.1Minecraft: 1.21.3fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.21.2)
1.3.0-snapshot.8+1.21.2-fabric18 авг. 2026 г.2Minecraft: 1.21.2fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.21.1)
1.3.0-snapshot.8+1.21.1-fabric18 авг. 2026 г.33Minecraft: 1.21.1fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.21)
1.3.0-snapshot.8+1.21-fabric18 авг. 2026 г.3Minecraft: 1.21fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.20.6)
1.3.0-snapshot.8+1.20.6-fabric18 авг. 2026 г.1Minecraft: 1.20.6fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.20.5)
1.3.0-snapshot.8+1.20.5-fabric18 авг. 2026 г.1Minecraft: 1.20.5fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.20.4)
1.3.0-snapshot.8+1.20.4-fabric18 авг. 2026 г.8Minecraft: 1.20.4fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.20.3)
1.3.0-snapshot.8+1.20.3-fabric18 авг. 2026 г.2Minecraft: 1.20.3fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.20.2)
1.3.0-snapshot.8+1.20.2-fabric18 авг. 2026 г.1Minecraft: 1.20.2fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.20.1)
1.3.0-snapshot.8+1.20.1-fabric18 авг. 2026 г.15Minecraft: 1.20.1fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.8 (Fabric 1.20)
1.3.0-snapshot.8+1.20-fabric18 авг. 2026 г.3Minecraft: 1.20fabric
Что нового
**Eighth snapshot of the 1.3.0 line.** A maintenance build: dependency and toolchain updates with no change to transform behavior.
### Changed
- Updates ASM, the bytecode library behind every transform, from 9.8 to 9.10.1. The supported Java and Minecraft ranges are unchanged, and one jar still runs on Java 17, 21, and 25.
- Updates SLF4J from 2.0.9 to 2.0.18, the enforcer plugin to 3.6.3, the source plugin to 3.4.0, and the test-only log4j API to 2.26.1.
- Updates the build workflow actions to their current major versions, keeping the commit-pinned form they already used.
- Keeps the Fabric loader compile dependency at 0.16.10. The running loader supplies it, so building against a newer release only risks calling an API that the older loaders Retromod accepts do not have.
- Derives the release artifact test's expected paths from its version constant, so a version bump no longer breaks its own mismatch cases.
Because this snapshot changes the bytecode library rather than any single transform rule, it was verified by re-transforming mods from scratch on 26.2 Fabric and 26.2 NeoForge clients, not by the test suite alone.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 26.2)
1.3.0-snapshot.7+26.2-neoforge13 авг. 2026 г.63Minecraft: 26.2neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 26.1.2)
1.3.0-snapshot.7+26.1.2-neoforge13 авг. 2026 г.32Minecraft: 26.1.2neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 26.1.1)
1.3.0-snapshot.7+26.1.1-neoforge13 авг. 2026 г.3Minecraft: 26.1.1neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 26.1)
1.3.0-snapshot.7+26.1-neoforge13 авг. 2026 г.2Minecraft: 26.1neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.21.11)
1.3.0-snapshot.7+1.21.11-nf13 авг. 2026 г.10Minecraft: 1.21.11neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.21.10)
1.3.0-snapshot.7+1.21.10-nf13 авг. 2026 г.7Minecraft: 1.21.10neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.21.9)
1.3.0-snapshot.7+1.21.9-neoforge13 авг. 2026 г.3Minecraft: 1.21.9neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.21.8)
1.3.0-snapshot.7+1.21.8-neoforge13 авг. 2026 г.5Minecraft: 1.21.8neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.21.7)
1.3.0-snapshot.7+1.21.7-neoforge13 авг. 2026 г.0Minecraft: 1.21.7neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.21.6)
1.3.0-snapshot.7+1.21.6-neoforge13 авг. 2026 г.1Minecraft: 1.21.6neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.21.5)
1.3.0-snapshot.7+1.21.5-neoforge13 авг. 2026 г.2Minecraft: 1.21.5neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.21.4)
1.3.0-snapshot.7+1.21.4-neoforge13 авг. 2026 г.7Minecraft: 1.21.4neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.21.3)
1.3.0-snapshot.7+1.21.3-neoforge13 авг. 2026 г.1Minecraft: 1.21.3neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.21.2)
1.3.0-snapshot.7+1.21.2-neoforge13 авг. 2026 г.1Minecraft: 1.21.2neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.21.1)
1.3.0-snapshot.7+1.21.1-neoforge13 авг. 2026 г.100Minecraft: 1.21.1neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.21)
1.3.0-snapshot.7+1.21-neoforge13 авг. 2026 г.5Minecraft: 1.21neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.20.6)
1.3.0-snapshot.7+1.20.6-neoforge13 авг. 2026 г.0Minecraft: 1.20.6neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.20.5)
1.3.0-snapshot.7+1.20.5-neoforge13 авг. 2026 г.1Minecraft: 1.20.5neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.20.4)
1.3.0-snapshot.7+1.20.4-neoforge13 авг. 2026 г.1Minecraft: 1.20.4neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.20.3)
1.3.0-snapshot.7+1.20.3-neoforge13 авг. 2026 г.1Minecraft: 1.20.3neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.20.2)
1.3.0-snapshot.7+1.20.2-neoforge13 авг. 2026 г.4Minecraft: 1.20.2neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (NeoForge 1.20.1)
1.3.0-snapshot.7+1.20.1-neoforge13 авг. 2026 г.5Minecraft: 1.20.1neoforge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 26.2)
1.3.0-snapshot.7+26.2-forge13 авг. 2026 г.22Minecraft: 26.2forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 26.1.2)
1.3.0-snapshot.7+26.1.2-forge13 авг. 2026 г.5Minecraft: 26.1.2forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 26.1.1)
1.3.0-snapshot.7+26.1.1-forge13 авг. 2026 г.0Minecraft: 26.1.1forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 26.1)
1.3.0-snapshot.7+26.1-forge13 авг. 2026 г.3Minecraft: 26.1forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.21.11)
1.3.0-snapshot.7+1.21.11-forge13 авг. 2026 г.17Minecraft: 1.21.11forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.21.10)
1.3.0-snapshot.7+1.21.10-forge13 авг. 2026 г.1Minecraft: 1.21.10forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.21.9)
1.3.0-snapshot.7+1.21.9-forge13 авг. 2026 г.2Minecraft: 1.21.9forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.21.8)
1.3.0-snapshot.7+1.21.8-forge13 авг. 2026 г.2Minecraft: 1.21.8forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.21.7)
1.3.0-snapshot.7+1.21.7-forge13 авг. 2026 г.2Minecraft: 1.21.7forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.21.6)
1.3.0-snapshot.7+1.21.6-forge13 авг. 2026 г.1Minecraft: 1.21.6forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.21.5)
1.3.0-snapshot.7+1.21.5-forge13 авг. 2026 г.3Minecraft: 1.21.5forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.21.4)
1.3.0-snapshot.7+1.21.4-forge13 авг. 2026 г.1Minecraft: 1.21.4forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.21.3)
1.3.0-snapshot.7+1.21.3-forge13 авг. 2026 г.0Minecraft: 1.21.3forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.21.2)
1.3.0-snapshot.7+1.21.2-forge13 авг. 2026 г.5Minecraft: 1.21.2forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.21.1)
1.3.0-snapshot.7+1.21.1-forge13 авг. 2026 г.20Minecraft: 1.21.1forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.21)
1.3.0-snapshot.7+1.21-forge13 авг. 2026 г.0Minecraft: 1.21forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.20.6)
1.3.0-snapshot.7+1.20.6-forge13 авг. 2026 г.0Minecraft: 1.20.6forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.20.5)
1.3.0-snapshot.7+1.20.5-forge13 авг. 2026 г.1Minecraft: 1.20.5forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.20.4)
1.3.0-snapshot.7+1.20.4-forge13 авг. 2026 г.2Minecraft: 1.20.4forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.20.3)
1.3.0-snapshot.7+1.20.3-forge13 авг. 2026 г.0Minecraft: 1.20.3forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.20.2)
1.3.0-snapshot.7+1.20.2-forge13 авг. 2026 г.1Minecraft: 1.20.2forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.20.1)
1.3.0-snapshot.7+1.20.1-forge13 авг. 2026 г.130Minecraft: 1.20.1forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Forge 1.20)
1.3.0-snapshot.7+1.20-forge13 авг. 2026 г.1Minecraft: 1.20forge
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 26.2)
1.3.0-snapshot.7+26.2-fabric13 авг. 2026 г.293Minecraft: 26.2fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 26.1.2)
1.3.0-snapshot.7+26.1.2-fabric13 авг. 2026 г.48Minecraft: 26.1.2fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 26.1.1)
1.3.0-snapshot.7+26.1.1-fabric13 авг. 2026 г.3Minecraft: 26.1.1fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 26.1)
1.3.0-snapshot.7+26.1-fabric13 авг. 2026 г.8Minecraft: 26.1fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.21.11)
1.3.0-snapshot.7+1.21.11-fabric13 авг. 2026 г.63Minecraft: 1.21.11fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.21.10)
1.3.0-snapshot.7+1.21.10-fabric13 авг. 2026 г.9Minecraft: 1.21.10fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.21.9)
1.3.0-snapshot.7+1.21.9-fabric13 авг. 2026 г.0Minecraft: 1.21.9fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.21.8)
1.3.0-snapshot.7+1.21.8-fabric13 авг. 2026 г.9Minecraft: 1.21.8fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.21.7)
1.3.0-snapshot.7+1.21.7-fabric13 авг. 2026 г.1Minecraft: 1.21.7fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.21.6)
1.3.0-snapshot.7+1.21.6-fabric13 авг. 2026 г.3Minecraft: 1.21.6fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.21.5)
1.3.0-snapshot.7+1.21.5-fabric13 авг. 2026 г.3Minecraft: 1.21.5fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.21.4)
1.3.0-snapshot.7+1.21.4-fabric13 авг. 2026 г.3Minecraft: 1.21.4fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.21.3)
1.3.0-snapshot.7+1.21.3-fabric13 авг. 2026 г.2Minecraft: 1.21.3fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.21.2)
1.3.0-snapshot.7+1.21.2-fabric13 авг. 2026 г.3Minecraft: 1.21.2fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.21.1)
1.3.0-snapshot.7+1.21.1-fabric13 авг. 2026 г.38Minecraft: 1.21.1fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.21)
1.3.0-snapshot.7+1.21-fabric13 авг. 2026 г.3Minecraft: 1.21fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.20.6)
1.3.0-snapshot.7+1.20.6-fabric13 авг. 2026 г.1Minecraft: 1.20.6fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.20.5)
1.3.0-snapshot.7+1.20.5-fabric13 авг. 2026 г.1Minecraft: 1.20.5fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.20.4)
1.3.0-snapshot.7+1.20.4-fabric13 авг. 2026 г.3Minecraft: 1.20.4fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.20.3)
1.3.0-snapshot.7+1.20.3-fabric13 авг. 2026 г.3Minecraft: 1.20.3fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.20.2)
1.3.0-snapshot.7+1.20.2-fabric13 авг. 2026 г.2Minecraft: 1.20.2fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.20.1)
1.3.0-snapshot.7+1.20.1-fabric13 авг. 2026 г.16Minecraft: 1.20.1fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.7 (Fabric 1.20)
1.3.0-snapshot.7+1.20-fabric13 авг. 2026 г.3Minecraft: 1.20fabric
Что нового
**Seventh snapshot of the 1.3.0 line.**
### Fixed
- **Refmap-only target changes can now repair parameter-capturing Mixin handlers.** Some Fabric mixins keep a Yarn source name in their annotation and rely on the refmap to locate the intermediary target. Snapshot.6 could update that refmap target, but the later class pass could not connect the source selector to the proven current method, so a handler that captured the old parameters stayed incompatible. The refmap pass now carries a uniquely proven target parameter addition into the matching class repair. Retromod inserts the unused parameters into the handler while leaving its source selector intact for Mixin to resolve through the refmap. Ambiguous targets, `remap = false`, unsafe parameter annotations, staticness mismatches, and owner mismatches are left unchanged. The shared repair context reaches runtime transforms, the CLI, normal and full AOT, and mixins in nested jars.
- **MixinExtras value modifiers can now keep complete target-argument captures after a safe signature change.** A `@ModifyReturnValue` or `@ModifyExpressionValue` handler may receive the intercepted value followed by every argument from its enclosing target. Snapshot.6 could update the selector when that target gained parameters, but left the handler with the old capture list, so Mixin still rejected it. Retromod now inserts each uniquely proven new target parameter after the leading value, for both direct selectors and targets related through a refmap. Value-only handlers keep their descriptor. Partial or extra captures, return-type changes, ambiguous layouts, `remap = false`, unsafe parameter annotations, and staticness mismatches remain untouched. Tested with direct and refmap-linked repairs, a two-slot parameter insertion, value-only handlers, and byte-for-byte refusal of partial captures (`AutomaticMixinTranslatorTest`).
- **Fabric runtime transforms now reach jars inside bundled libraries.** A Fabric mod can bundle a library that carries another jar under `META-INF/jars/` or `META-INF/jarjar/`. The runtime path previously stopped after the first library, leaving deeper classes and compatibility resources in their old namespace. It now applies the nested class, mixin, refmap, access-widener, and generated-helper passes recursively through four levels below the outer mod. Generated helpers use the complete archive chain as their stable key, so identical child filenames cannot collide. Tested with a second-level class remap, official refmap and access-widener output, and full-chain helper relocation (`FabricNestedJarRecursionTest`).
- **CLI verification now checks the exact Minecraft jar supplied for the transform.** `--verify` previously looked only at classes visible to the standalone CLI process. That produced false missing-class reports for valid 26.2 classes such as `EntityTypes`, even when the same command had indexed them through `--mc-jar`. Class, method, field, and constructor checks now use that target index when available, while loader runs keep their live-classpath fallback. Covered by a standalone-classpath regression (`TransformVerifierTest`) and a verified 1.20.1 to 26.2 test-mod transform.
- **Verification now finds stale Minecraft links inside recursively bundled libraries.** With an exact target index, Retromod checks each game-owned class, method descriptor, field descriptor, and constructor descriptor in nested jars through the same recursion limit as the transform. Other nested third-party references remain intentionally ignored because bundled libraries commonly contain optional integrations that are absent at runtime. Tested with a stale target reference two jars deep and an absent optional dependency (`TransformVerifierTest`).
- **Exact CLI reports no longer list Minecraft's separate runtime libraries as missing client classes.** DataFixerUpper packages such as `com.mojang.serialization` live beside the client jar rather than inside it, while SLF4J and Gson keep their original runtime packages even though the executable CLI relocates its private copies. The verifier now separates those libraries from the game-owned `com.mojang` namespaces and builds relocation-sensitive allow-list prefixes at runtime. Revamped Phantoms and the Fabric test mod now produce compact reports with those false positives removed.
- **Legacy screen super-key calls now construct the 26.x input event correctly.** A transformed `super.keyPressed(key, scanCode, modifiers)` still called the removed primitive overload, so the class could load but failed as soon as that path executed. Retromod now adapts the exact `Screen.keyPressed(int, int, int)` call site by constructing `KeyEvent` and preserves `INVOKESPECIAL` dispatch to the direct superclass. It does not rename old mod overrides or enable the broad bridge generator. Covered by an executable transform regression that checks all three values, plus the 1.20.1 Fabric test mod transformed against the real 26.2 client jar.
- **Fabric mixins declared through `mixin.<modid>.json` are repaired at runtime.** The config discovery pass accepted several common names but missed this singular-prefix form. Classes listed only there received the ordinary bytecode remap while their annotation selectors stayed intermediary, causing mixin application to fail on 26.x. The discovery regression and Fabric test mod now use this exact naming shape, matching Revamped Phantoms.
- **Remapped Fabric class hierarchies keep precise stack-map frames.** Frame rebuilding could read a mod class from its original jar but then follow the intermediary superclass name after the class itself had already moved to the official namespace. A branch joining that mod class with one of its Minecraft ancestors therefore widened to `Object`, and the JVM rejected a later typed call with `VerifyError: Bad type on operand stack`. Hierarchy parents read from source bytes now pass through the active class remapper, then continue through the exact target-jar index when the host classes are outside the standalone transform classpath. Covered by an executable frame regression and the reported Revamped Phantoms `Shockwave` transform.
- **Revamped Phantoms now crosses the remaining 26.x entity API links found by exact verification.** The 26.1 compatibility layer now rebuilds `EntityType.Builder.build(String)`, entity-type tag checks, combined damage calls, phantom target conditions, predicate selectors, nearby-entity queries, and camera orientation from APIs present on both 26.1 and 26.2. Server-only queries are guarded instead of casting a client level. Focused bytecode tests cover every redirected descriptor, and the Fabric test mod exercises the old target-predicate and entity-type tag calls.
- **Legacy entity renderers can reach the 26.x render-state contract without a linkage crash.** A tightly scoped adapter supplies the missing base `EntityRenderState` factory and removes only the obsolete direct `super.render(...)` call from concrete renderers compiled for the old six-argument API. It leaves modern, indirect, abstract, Minecraft-owned, and unknown buffer shapes unchanged. This is load-safety staging, not a visual port: old custom geometry is not connected to the modern submit pipeline and may remain invisible. Covered by an executable class-loading regression.
- **CLI refmaps whose filenames also contain `mixin` stay on the refmap path.** Names such as `mixins.modid.refmap.json` previously matched the broad mixin-config check first, so the class pass could use the prepared repair while the emitted resource kept its old selector. Refmaps are now routed before configs in outer mods and nested libraries. Both paths have regressions.
- **AutoClicky reaches a stable 26.2 client startup (#180).** The remaining client calls now follow the 26.x system-message, interaction, creative inventory, swing, rendering, tooltip, keyboard, and mouse APIs. Legacy screen and widget overrides receive hierarchy-checked bridges, and the retired Fabric API dependency id is updated in runtime, CLI, and AOT metadata paths. The exact AutoClicky 1.2.1 jar verifies without unresolved references on 26.1.2 and 26.2. A live 26.2 client completed mod initialization, resource reload, renderer setup, sound startup, and atlas creation. Its settings controls and automated actions still need gameplay testing.
- **Double Hotbar's HUD mixin follows the complete 26.x hotbar move (#181).** Retromod now recognizes the exact seven-handler mixin shape, renames its extraction selectors and slot shadow, and moves the target from `Gui` to `Hud` on 26.2 without applying a global class redirect. Its old inventory swap call now uses `ContainerInput.SWAP` and `handleContainerInput`. The exact 1.3.4 jar transforms cleanly for 26.1.2 and 26.2, and a 26.2 client survived stable startup without a mixin failure. This replaces the snapshot.4 limitation that said the second hotbar required a manual port.
- **ENGRAM no longer keeps stale inherited world calls or a half-active screenshot mixin on Fabric 1.21.11 (#179).** Exact method redirects may opt into hierarchy-aware matching when the jar or host index proves that a mod-owned call owner inherits the renamed Minecraft member. This repairs all ten subclass-owned entity world calls in the reported jar without guessing for unrelated owners. ENGRAM's paired screenshot mixins are disabled together only on the verified 1.21.11 target, so the optional screenshot path is inert instead of failing after its delayed task starts. The exact jar reached a live title screen through sound and atlas startup with no accessor failure.
- **An old Fabric API in the input folder no longer becomes a second installed API (#186).** Fabric pre-launch identifies the top-level API by metadata. When a target-native Fabric API is already loaded, Retromod archives the staged old jar byte-for-byte and creates no transformed output. If the host API is missing, it keeps the source in place and prints an actionable message. Both input roots share the guard, and the Fabric test mod asserts that exactly one API provider is loaded.
- **Forge 1.20.6 and newer receive Mojang member names instead of target SRG names (#204).** Forge changed its runtime namespace before 26.x, so transforming CoFH Core for Forge 1.21.1 with a 1.20.1 target-SRG table left its reported mixin shadow unresolved. Runtime, CLI, and AOT mapping setup now select official names from 1.20.6 onward. Exact CoFH bytecode reaches the live `LivingEntity.getUseItem` method, and Forge 1.21.1 starts past the reported mixin failure.
- **Legacy Forge 1.20.1 channels can construct on Forge 1.20.6 through 1.21.x (#204).** A per-mod helper adapts the removed nested channel builder, string protocol supplier, version predicates, event channel, payload event, and context access to that Forge networking surface. Missing, vanilla, and advertised-version predicate inputs retain their legacy meaning. The exact CoFH Core jar proceeds past its removed channel-builder failure on a real Forge 1.21.1 server. Forge 26.x removed event-object registration again, so this bridge stays disabled there until it has a separate listener adapter.
- **Forge distribution markers stay in the Forge namespace.** Annotation migration previously moved `Dist` and `OnlyIn` to NeoForge even when the target loader was Forge. The migration is now loader-gated, with a regression proving that a Forge target retains its live classes.
- **Legacy class-only AccessTransformer entries are accepted by current NeoForge (#221).** Some old Forge jars wrote a class target with a trailing descriptor semicolon. Retromod removes it only from class-only lines and preserves valid semicolons inside member descriptors. Runtime, CLI, AOT, metadata-only, and nested-jar paths share the normalization. NeoForge's current parser accepted all 69 entries from the exact transformed Mana and Artifice jar.
- **Management Wanted's reported companion tick listeners are concrete on NeoForge 26.1.2 (#207).** Old Forge `ServerTickEvent` and `PlayerTickEvent` listeners now target the current `Post` events while preserving their `END` phase checks. The companion audit also moves legacy FML side and mod-info types, uses the embedded spawn-egg bridge, removes Forge's incompatible loader-version floor during metadata promotion, and erases the deleted return type from discarded Forge 47 message registrations. Exact transformed copies of More Decor 2.7 and Management Wanted Fixes 1.0.3 contain those repaired shapes.
- **Forge and NeoForge users are directed through the pre-scan input path.** The installation and troubleshooting pages now explain that an incompatible version range can be rejected during loader discovery before Retromod's entry point exists. Old jars should start in `retromod-input/` or be prepared with the CLI; in-place transformation remains available only when the loader already accepts the source metadata.
- **Dedicated Forge servers are no longer mistaken for clients.** Current Forge server jars contain client class resources, so the old presence check returned the wrong side even during a `forge_server` launch. Environment detection now asks Fabric, Forge, or NeoForge for its selected side before using class resources as a fallback. The probe still does not define or initialize Minecraft classes.
### Known limitations
- **CoFH Core and Thermal still need an enchantment-system port on Forge 1.21.1 (#204).** The exact jar now passes the reported mixin namespace error and the removed networking builder. Its next construction failure reads the deleted static enchantment registry, and its own enchantment classes subclass the old `Enchantment` type. Minecraft 1.21 made enchantments final, data-driven values, so replacing one field would only move the crash into object construction. Retromod leaves this structural change for a real data and behavior port.
- **The reported Medieval Origins bundle still needs a corrected dependency set (#186).** The duplicate Fabric API path is fixed, but Medieval Origins Revival 7.0.4-pre.2 requires Origins 1.13.0-pre.2, Spell Engine, and Icarus up to 4.6.4. The submitted bundle used Origins pre.3 and omitted the other two dependencies. Keep the issue open until that corrected bundle receives an in-game run.
- **Management Wanted still needs a complete NeoForge 26.1.2 launch (#207).** The reported abstract-event failure and several following link failures are fixed, but the companion jars use deeper Forge capability, menu, client-extension, and networking behavior. The current legacy network wrapper records packet registrations without fully restoring packet delivery. Keep the issue open until the original three-jar bundle is tested in game.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 26.2)
1.3.0-snapshot.6+26.2-neoforge11 авг. 2026 г.10Minecraft: 26.2neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 26.1.2)
1.3.0-snapshot.6+26.1.2-neoforge11 авг. 2026 г.19Minecraft: 26.1.2neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 26.1.1)
1.3.0-snapshot.6+26.1.1-neoforge11 авг. 2026 г.4Minecraft: 26.1.1neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 26.1)
1.3.0-snapshot.6+26.1-neoforge11 авг. 2026 г.1Minecraft: 26.1neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.21.11)
1.3.0-snapshot.6+1.21.11-nf11 авг. 2026 г.2Minecraft: 1.21.11neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.21.10)
1.3.0-snapshot.6+1.21.10-nf11 авг. 2026 г.8Minecraft: 1.21.10neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.21.9)
1.3.0-snapshot.6+1.21.9-neoforge11 авг. 2026 г.2Minecraft: 1.21.9neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.21.8)
1.3.0-snapshot.6+1.21.8-neoforge11 авг. 2026 г.8Minecraft: 1.21.8neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.21.7)
1.3.0-snapshot.6+1.21.7-neoforge11 авг. 2026 г.2Minecraft: 1.21.7neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.21.6)
1.3.0-snapshot.6+1.21.6-neoforge11 авг. 2026 г.2Minecraft: 1.21.6neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.21.5)
1.3.0-snapshot.6+1.21.5-neoforge11 авг. 2026 г.1Minecraft: 1.21.5neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.21.4)
1.3.0-snapshot.6+1.21.4-neoforge11 авг. 2026 г.1Minecraft: 1.21.4neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.21.3)
1.3.0-snapshot.6+1.21.3-neoforge11 авг. 2026 г.2Minecraft: 1.21.3neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.21.2)
1.3.0-snapshot.6+1.21.2-neoforge11 авг. 2026 г.3Minecraft: 1.21.2neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.21.1)
1.3.0-snapshot.6+1.21.1-neoforge11 авг. 2026 г.37Minecraft: 1.21.1neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.21)
1.3.0-snapshot.6+1.21-neoforge11 авг. 2026 г.2Minecraft: 1.21neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.20.6)
1.3.0-snapshot.6+1.20.6-neoforge11 авг. 2026 г.3Minecraft: 1.20.6neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.20.5)
1.3.0-snapshot.6+1.20.5-neoforge11 авг. 2026 г.3Minecraft: 1.20.5neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.20.4)
1.3.0-snapshot.6+1.20.4-neoforge11 авг. 2026 г.1Minecraft: 1.20.4neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.20.3)
1.3.0-snapshot.6+1.20.3-neoforge11 авг. 2026 г.4Minecraft: 1.20.3neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.20.2)
1.3.0-snapshot.6+1.20.2-neoforge11 авг. 2026 г.2Minecraft: 1.20.2neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (NeoForge 1.20.1)
1.3.0-snapshot.6+1.20.1-neoforge11 авг. 2026 г.7Minecraft: 1.20.1neoforge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 26.2)
1.3.0-snapshot.6+26.2-forge11 авг. 2026 г.6Minecraft: 26.2forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 26.1.2)
1.3.0-snapshot.6+26.1.2-forge11 авг. 2026 г.4Minecraft: 26.1.2forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 26.1.1)
1.3.0-snapshot.6+26.1.1-forge11 авг. 2026 г.3Minecraft: 26.1.1forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 26.1)
1.3.0-snapshot.6+26.1-forge11 авг. 2026 г.4Minecraft: 26.1forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.21.11)
1.3.0-snapshot.6+1.21.11-forge11 авг. 2026 г.3Minecraft: 1.21.11forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.21.10)
1.3.0-snapshot.6+1.21.10-forge11 авг. 2026 г.3Minecraft: 1.21.10forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.21.9)
1.3.0-snapshot.6+1.21.9-forge11 авг. 2026 г.3Minecraft: 1.21.9forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.21.8)
1.3.0-snapshot.6+1.21.8-forge11 авг. 2026 г.4Minecraft: 1.21.8forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.21.7)
1.3.0-snapshot.6+1.21.7-forge11 авг. 2026 г.1Minecraft: 1.21.7forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.21.6)
1.3.0-snapshot.6+1.21.6-forge11 авг. 2026 г.1Minecraft: 1.21.6forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.21.5)
1.3.0-snapshot.6+1.21.5-forge11 авг. 2026 г.3Minecraft: 1.21.5forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.21.4)
1.3.0-snapshot.6+1.21.4-forge11 авг. 2026 г.2Minecraft: 1.21.4forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.21.3)
1.3.0-snapshot.6+1.21.3-forge11 авг. 2026 г.2Minecraft: 1.21.3forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.21.2)
1.3.0-snapshot.6+1.21.2-forge11 авг. 2026 г.3Minecraft: 1.21.2forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.21.1)
1.3.0-snapshot.6+1.21.1-forge11 авг. 2026 г.7Minecraft: 1.21.1forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.21)
1.3.0-snapshot.6+1.21-forge11 авг. 2026 г.4Minecraft: 1.21forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.20.6)
1.3.0-snapshot.6+1.20.6-forge11 авг. 2026 г.5Minecraft: 1.20.6forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.20.5)
1.3.0-snapshot.6+1.20.5-forge11 авг. 2026 г.1Minecraft: 1.20.5forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.20.4)
1.3.0-snapshot.6+1.20.4-forge11 авг. 2026 г.2Minecraft: 1.20.4forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.20.3)
1.3.0-snapshot.6+1.20.3-forge11 авг. 2026 г.4Minecraft: 1.20.3forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.20.2)
1.3.0-snapshot.6+1.20.2-forge11 авг. 2026 г.4Minecraft: 1.20.2forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.20.1)
1.3.0-snapshot.6+1.20.1-forge11 авг. 2026 г.27Minecraft: 1.20.1forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Forge 1.20)
1.3.0-snapshot.6+1.20-forge11 авг. 2026 г.2Minecraft: 1.20forge
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 26.2)
1.3.0-snapshot.6+26.2-fabric11 авг. 2026 г.71Minecraft: 26.2fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 26.1.2)
1.3.0-snapshot.6+26.1.2-fabric11 авг. 2026 г.13Minecraft: 26.1.2fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 26.1.1)
1.3.0-snapshot.6+26.1.1-fabric11 авг. 2026 г.2Minecraft: 26.1.1fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 26.1)
1.3.0-snapshot.6+26.1-fabric11 авг. 2026 г.4Minecraft: 26.1fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.21.11)
1.3.0-snapshot.6+1.21.11-fabric11 авг. 2026 г.9Minecraft: 1.21.11fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.21.10)
1.3.0-snapshot.6+1.21.10-fabric11 авг. 2026 г.1Minecraft: 1.21.10fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.21.9)
1.3.0-snapshot.6+1.21.9-fabric11 авг. 2026 г.0Minecraft: 1.21.9fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.21.8)
1.3.0-snapshot.6+1.21.8-fabric11 авг. 2026 г.1Minecraft: 1.21.8fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.21.7)
1.3.0-snapshot.6+1.21.7-fabric11 авг. 2026 г.1Minecraft: 1.21.7fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.21.6)
1.3.0-snapshot.6+1.21.6-fabric11 авг. 2026 г.0Minecraft: 1.21.6fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.21.5)
1.3.0-snapshot.6+1.21.5-fabric11 авг. 2026 г.0Minecraft: 1.21.5fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.21.4)
1.3.0-snapshot.6+1.21.4-fabric11 авг. 2026 г.11Minecraft: 1.21.4fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.21.3)
1.3.0-snapshot.6+1.21.3-fabric11 авг. 2026 г.1Minecraft: 1.21.3fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.21.2)
1.3.0-snapshot.6+1.21.2-fabric11 авг. 2026 г.1Minecraft: 1.21.2fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.21.1)
1.3.0-snapshot.6+1.21.1-fabric11 авг. 2026 г.25Minecraft: 1.21.1fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.21)
1.3.0-snapshot.6+1.21-fabric11 авг. 2026 г.2Minecraft: 1.21fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.20.6)
1.3.0-snapshot.6+1.20.6-fabric11 авг. 2026 г.1Minecraft: 1.20.6fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.20.5)
1.3.0-snapshot.6+1.20.5-fabric11 авг. 2026 г.2Minecraft: 1.20.5fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.20.4)
1.3.0-snapshot.6+1.20.4-fabric11 авг. 2026 г.1Minecraft: 1.20.4fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.20.3)
1.3.0-snapshot.6+1.20.3-fabric11 авг. 2026 г.3Minecraft: 1.20.3fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.20.2)
1.3.0-snapshot.6+1.20.2-fabric11 авг. 2026 г.2Minecraft: 1.20.2fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.20.1)
1.3.0-snapshot.6+1.20.1-fabric11 авг. 2026 г.5Minecraft: 1.20.1fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.6 (Fabric 1.20)
1.3.0-snapshot.6+1.20-fabric11 авг. 2026 г.2Minecraft: 1.20fabric
Что нового
**Sixth snapshot of the 1.3.0 line.**
### Added
- **Mixins can now follow safe target-signature changes automatically.** After normal namespace and shim remapping, Retromod checks each unresolved target against the exact methods declared by the current Minecraft jar. When one unique method preserves the old parameters in order and only adds up to three new parameters, it updates the selector and inserts unused handler parameters while preserving stack frames, locals, nullability annotations, and type annotations. Fabric refmap values, descriptor-shaped lookup keys, and existing official sections follow the same exact decision. This covers descriptor and bare-name `@Inject` targets, nested call targets used by `@Redirect` and MixinExtras `@WrapOperation`, `@Overwrite` methods, zero-capture HEAD/TAIL/RETURN injections, and tightly gated `@Invoker` renames. Ambiguous overloads, deleted or reordered parameters, type substitutions, changed return values, class-level, injector-level, or nested `@At` `remap = false`, and semantic local captures remain untouched. Verified against the exact No Chat Reports, Simple Voice Chat, Mouse Tweaks, and Dynamic FPS jars, plus a Fabric test-mod mixin compiled for 1.20.1 and focused refusal regressions.
- **Offline transforms can use the same exact host method index.** `transform`, `batch`, and `aot` now accept `--mc-jar <target.jar>`. The CLI reads `version.json` from that jar, unless `--target` was supplied, so version-gated shims and the method index cannot silently disagree. Loader transforms continue to discover and index the running Minecraft jar automatically.
### Fixed
- **Release builds now regenerate the external SHA-256 manifest.** `build-all.sh` replaces the stale checksum file with one entry for every current distribution jar and refuses a release matrix whose checksum count is incomplete. Snapshot.6 therefore publishes 69 checksums for its 69 artifacts: 68 loader jars and one CLI jar.
- **Offline transforms reject a target version that disagrees with `--mc-jar`.** If the Minecraft jar exposes a version, an explicit `--target` must now match it instead of selecting one shim chain while indexing a different host API. Jars without readable version metadata still support an explicit target and report that inference was unavailable. Covered by focused CLI regressions.
- **Batch verification now covers every produced jar.** Verification also runs for unchanged copies, so `--verify` no longer leaves a blind spot when metadata says a mod already targets the selected host.
- **The first Fabric pre-launch uses the documented config defaults.** Retromod now creates its default config before processing `retromod-input/`, so transform verification and the other defaults apply on the first launch instead of only after normal mod initialization.
- **Saving the in-game config no longer discards manual settings.** The screen shows the four settings that currently affect runtime behavior and merges those edits into the existing JSON. Reserved fields, the manual Modrinth opt-in, explanatory fields, and future keys survive a save. The `polyfills_enabled` switch is now honored during Fabric pre-launch and on Forge as well as NeoForge. Covered by config parsing and round-trip regressions.
- **The self-integrity check resolves encoded jar paths.** A normal Prism Launcher location such as `Application Support` appears in a code-source URL with `%20`. Treating that URL text as a filesystem path made the release verifier return `UNKNOWN` without its expected startup line. The verifier now resolves file and nested jar URLs through `URI`, with regressions for encoded spaces, class directories, and non-file URLs.
- **Dynamic FPS mixins cross both of its reported 26.2 signature changes.** An exact-prefix `@Inject` handler can now gain trailing target parameters before its `CallbackInfo`, which repairs the added integer parameter on `SoundEngine.play`. Source metadata such as `1.21.0` is also normalized to the shim graph's `1.21` milestone, so the full chain runs and `Window.getWindow()` reaches `Window.handle()`. The exact mod passed both repaired mixin checks in a live 26.2 launch. Tested with focused descriptor, shim-chain, and Fabric load-time regressions.
- **Mixin shadow and accessor fields are resolved against their declared target owner.** A redirect for `Window.window` previously renamed an unrelated `Minecraft.window` shadow because both fields shared a bare name. Field declarations, self-owned bytecode references, accessors, and explicit `@At(FIELD)` selectors now use owner and descriptor together, and field-to-method redirects are rejected. Tested with matching, unrelated-owner, descriptor-mismatch, and explicit-owner cases.
- **No Chat Reports crosses two removed client APIs on 26.2.** Its chat options mixin now rebases from `SimpleOptionsSubScreen` to `OptionsSubScreen` and adjusts the old constructor call. The post-remap repair also recognizes the temporary placeholder used by the Fabric runtime path, while that placeholder stays disabled on pre-26.1 hosts where the intermediary class is still live. Its deleted client-command parser invoker is replaced with a default interface bridge that parses through the current command dispatcher and suggestions provider. The exact mod passed both repaired mixin checks in a live 26.2 launch, with Fabric load-time regressions for each bridge.
- **Legacy Fabric client tick callbacks fire again.** The callback bridge created and accepted old listener registrations, but tried to invoke `register` through Fabric API's package-private event implementation. Current Java rejected that reflective call, leaving the listeners inert. The bridge now invokes the public `Event` interface method. Covered by a regression with a private event implementation and a 26.2 title-screen launch with Chat Bubbles installed.
- **Legacy overlay checks no longer crash on 26.2.** `Minecraft.getOverlay()` and `setOverlay()` moved behind the client's `gui` object. Retromod now uses a generated receiver-hop bridge for both calls, with the exact Chat Bubbles 1.0.1 bytecode and the real 26.2 field and method descriptors covered by focused tests. This does not make Chat Bubbles fully compatible: its only chat-capture mixin and its player-renderer replacement target removed systems, so the bubble feature still needs a manual port.
- **Fabric mods keep the live `BlockState.getBlock()` method.** A stale shim renamed it to the nonexistent `getBlockType()` after the intermediary map had already selected the correct method, which made the test mod fail its block-state round trip at initialization. The bad override is removed and the full Fabric registration order is covered by a regression.
- **Constructor redirects can no longer fall into ordinary method heuristics.** JVM constructors and class initializers are excluded from fuzzy and pattern-based method matching, which could otherwise leave an uninitialized object on the stack. Fifteen legacy shim registrations now use the dedicated constructor factory API or a class move, making the intended stack rewrite explicit. Tested with heuristic refusal cases and a registration audit across the shim set.
- **Generated static Mixin helpers are accepted by current Mixin.** Pose-constructor and worldgen-codec invokers become mixin-owned helpers after their descriptor bridge is generated. Leaving those helpers public made Mixin reject the class before it could merge them. Static helpers are now private, which also gives them the intended access to the target's private constructor or registrar. Covered by focused access regressions and the Fabric load-time test mixins.
- **Relocated Fabric callback bridges keep each mod's real interface identity.** Per-mod synthetic interfaces are intentionally relocated to avoid JPMS split packages, but the item-group, server-world, and entity-model-layer bridges still created events or reflective methods from their original generated interfaces. That produced `ArrayStoreException` during registration or an `IllegalArgumentException` during resource reload. Generated holders now pass remappable class literals, caches include the relocated type, and delayed providers resolve the interface implemented by the callback object. Tested through actual synthetic embedding, deferred invocation, and the Fabric in-game bridge harness.
- **Generated compatibility helpers are isolated across every nested-jar path.** Fabric previously copied shared `com/retromod/generated` classes into every transformed mod, so an older jar could win Knot's class lookup and expose a stale bridge signature to newly transformed mods. Fabric now embeds only the helpers a mod references under a stable per-mod name, while old no-argument callback holders remain supported. Fabric jar-in-jar, CLI, and AOT recursion apply the same relocation inside each nested library. If relocation cannot complete, the original nested jar is kept byte-for-byte instead of publishing dangling helper references. Tests cover recursive keys, manifests, directory entries, collisions, archive limits, and old and new bridge descriptors.
- **Multiple translated HUD callback mods can render together.** Every relocated `HudRenderCallback` previously registered the same `retromod:legacy_hud_render` layer id, so Fabric accepted the first mod and left later callbacks inert. Each callback type now receives a stable, valid, collision-resistant layer path derived from its relocated identity. Tested with two independent callback types and a full synthetic-bridge audit.
- **Mixed-loader offline batches no longer leak member namespaces between jars.** The singleton transformer could leave Forge target-SRG names active for the next Fabric mod, or Fabric intermediary names active for the next Forge mod. Loader member mappings are now rebuilt for each input jar. The same shared setup also reaches standalone AOT and `batch --aot`, which previously missed the new Forge 1.20.1 target-SRG layer entirely. Tested in both loader orders and through the public AOT jar path with an exact `func_180495_p` to `m_8055_` call.
- **Forge and NeoForge refmap repair now reaches AOT output and nested jars.** Refmap selectors are rewritten on the CLI, normal AOT, and recursive jar-in-jar paths with the same loader gate as the outer runtime transform. Fabric intermediary sections remain unchanged. Tested across outer and nested AOT resources and CLI nested jars.
- **Old Forge member names now cross into the exact Forge 1.20.1 SRG namespace (#192).** A 1.16 mod uses `func_` and `field_` names, while Forge 1.20.1 expects different `m_` and `f_` identifiers. Mapping the old names only to Mojang names works on unobfuscated 26.x but fails on pre-26 Forge. Retromod now joins source SRG, Mojang official names, and Forge 1.20.1 MCPConfig with owner and descriptor qualification, covering 39,079 methods and 30,036 fields without global-name guesses. Better Portals' reported block-state and entity-level references now resolve to the exact Forge 1.20.1 members. Unsupported Forge targets keep their source names instead of receiving a half-correct Mojang conversion. Tested with generated mapping-table validation, focused transform regressions, and the exact Better Portals 0.3.9 jar.
- **Forge mixin refmaps follow class and member remaps (#192).** Rewriting a mixin class is not enough because Forge resolves its annotation through a separate refmap resource. Better Portals' class annotation targeted `LocalPlayer`, but its refmap still supplied the removed `ClientPlayerEntity` owner and Mixin rejected the handler. Refmap keys and values now move together through owner-qualified class, descriptor, and target-SRG mapping on the CLI and runtime Forge paths, including nested jars. The reported client-player and portal-shape mixins now pass Mixin parsing on Forge 1.20.1. Tested with the exact two-entry refmap and a real client launch.
- **Legacy `BlockBehaviour.Properties.noDrops()` calls use `noLootTable()` on Forge 1.20.1 (#192).** The rename is owner-scoped and then resolved to the target SRG name, so unrelated methods are untouched. The reported mod now passes this construction call instead of failing with `NoSuchMethodError`. Tested with a focused mapping regression and a real client launch.
### Compatibility reports
- **Compatibility analysis now reports eager legacy Forge registration.** A mod that combines `RegistryEvent.Register` handlers with non-JDK object construction in static initializers is flagged as needing a `DeferredRegister` lifecycle migration. This catches the next Better Portals blocker, where its symbols all resolve but Forge rejects block construction after the registry is frozen. The analyzer now explains that structural limit instead of leaving users with only a clean linkage report. Tested with a synthetic legacy registry holder and the exact reported jar.
### Known limitations
- **A refmap-only Yarn method rename cannot yet supply newly captured handler parameters.** Retromod repairs the refmap target, so zero-capture injections keep working. If the annotation text still uses a different Yarn source name and its handler captures every old target parameter, the class pass cannot yet connect that source name back to the repaired refmap entry. These handlers remain unchanged for a reviewed repair instead of receiving a guessed parameter layout.
- **YUNG's Better Portals still needs a registry lifecycle port on Forge 1.20.1 (#192).** Snapshot.6 clears the missing class, old-to-target SRG members, stale mixin refmap, and removed `noDrops()` call. The exact mod and YUNG's API then reach mod construction, where Better Portals eagerly creates its blocks and fluids from static initializers. Forge 1.20.1 requires those objects to be supplied during its registry event through `DeferredRegister`; translating static fields, their consumers, fluids, blocks, items, and event timing is a structural mod port rather than a safe member redirect. The new analyzer reports this explicitly.
### Infrastructure
- **Forge 1.20.1 target mappings are reproducible.** `scripts/harvest-target-srg.py` joins Mojang's official mappings with Forge MCPConfig and writes the owner-and-descriptor-qualified target table used by the transformer.
- **The release matrix now covers every published target again.** NeoForge 26.1 and 26.1.1 are no longer skipped, and the script requires exactly 23 Fabric, 23 Forge, 22 NeoForge, and one CLI artifact. A complete snapshot.6 run produced all 69 jars with no failures.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 26.2)
1.3.0-snapshot.5+26.2-neoforge10 авг. 2026 г.18Minecraft: 26.2neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 26.1.2)
1.3.0-snapshot.5+26.1.2-neoforge10 авг. 2026 г.9Minecraft: 26.1.2neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.21)
1.3.0-snapshot.5+1.21-neoforge10 авг. 2026 г.1Minecraft: 1.21neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.21.9)
1.3.0-snapshot.5+1.21.9-neoforge10 авг. 2026 г.1Minecraft: 1.21.9neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.21.8)
1.3.0-snapshot.5+1.21.8-neoforge10 авг. 2026 г.3Minecraft: 1.21.8neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.21.7)
1.3.0-snapshot.5+1.21.7-neoforge10 авг. 2026 г.1Minecraft: 1.21.7neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.21.6)
1.3.0-snapshot.5+1.21.6-neoforge10 авг. 2026 г.1Minecraft: 1.21.6neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.21.5)
1.3.0-snapshot.5+1.21.5-neoforge10 авг. 2026 г.4Minecraft: 1.21.5neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.21.4)
1.3.0-snapshot.5+1.21.4-neoforge10 авг. 2026 г.1Minecraft: 1.21.4neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.21.3)
1.3.0-snapshot.5+1.21.3-neoforge10 авг. 2026 г.2Minecraft: 1.21.3neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.21.2)
1.3.0-snapshot.5+1.21.2-neoforge10 авг. 2026 г.4Minecraft: 1.21.2neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.21.11)
1.3.0-snapshot.5+1.21.11-nf10 авг. 2026 г.0Minecraft: 1.21.11neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.21.10)
1.3.0-snapshot.5+1.21.10-nf10 авг. 2026 г.1Minecraft: 1.21.10neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.21.1)
1.3.0-snapshot.5+1.21.1-neoforge10 авг. 2026 г.58Minecraft: 1.21.1neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.20.6)
1.3.0-snapshot.5+1.20.6-neoforge10 авг. 2026 г.0Minecraft: 1.20.6neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.20.5)
1.3.0-snapshot.5+1.20.5-neoforge10 авг. 2026 г.0Minecraft: 1.20.5neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.20.4)
1.3.0-snapshot.5+1.20.4-neoforge10 авг. 2026 г.1Minecraft: 1.20.4neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.20.3)
1.3.0-snapshot.5+1.20.3-neoforge10 авг. 2026 г.1Minecraft: 1.20.3neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.20.2)
1.3.0-snapshot.5+1.20.2-neoforge10 авг. 2026 г.3Minecraft: 1.20.2neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (NeoForge 1.20.1)
1.3.0-snapshot.5+1.20.1-neoforge10 авг. 2026 г.5Minecraft: 1.20.1neoforge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 26.2)
1.3.0-snapshot.5+26.2-forge10 авг. 2026 г.11Minecraft: 26.2forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 26.1)
1.3.0-snapshot.5+26.1-forge10 авг. 2026 г.2Minecraft: 26.1forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 26.1.2)
1.3.0-snapshot.5+26.1.2-forge10 авг. 2026 г.6Minecraft: 26.1.2forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 26.1.1)
1.3.0-snapshot.5+26.1.1-forge10 авг. 2026 г.3Minecraft: 26.1.1forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.21)
1.3.0-snapshot.5+1.21-forge10 авг. 2026 г.2Minecraft: 1.21forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.21.9)
1.3.0-snapshot.5+1.21.9-forge10 авг. 2026 г.4Minecraft: 1.21.9forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.21.8)
1.3.0-snapshot.5+1.21.8-forge10 авг. 2026 г.1Minecraft: 1.21.8forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.21.7)
1.3.0-snapshot.5+1.21.7-forge10 авг. 2026 г.0Minecraft: 1.21.7forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.21.6)
1.3.0-snapshot.5+1.21.6-forge10 авг. 2026 г.1Minecraft: 1.21.6forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.21.5)
1.3.0-snapshot.5+1.21.5-forge10 авг. 2026 г.0Minecraft: 1.21.5forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.21.4)
1.3.0-snapshot.5+1.21.4-forge10 авг. 2026 г.1Minecraft: 1.21.4forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.21.3)
1.3.0-snapshot.5+1.21.3-forge10 авг. 2026 г.0Minecraft: 1.21.3forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.21.2)
1.3.0-snapshot.5+1.21.2-forge10 авг. 2026 г.1Minecraft: 1.21.2forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.21.11)
1.3.0-snapshot.5+1.21.11-forge10 авг. 2026 г.8Minecraft: 1.21.11forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.21.10)
1.3.0-snapshot.5+1.21.10-forge10 авг. 2026 г.2Minecraft: 1.21.10forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.21.1)
1.3.0-snapshot.5+1.21.1-forge10 авг. 2026 г.13Minecraft: 1.21.1forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.20)
1.3.0-snapshot.5+1.20-forge10 авг. 2026 г.3Minecraft: 1.20forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.20.6)
1.3.0-snapshot.5+1.20.6-forge10 авг. 2026 г.1Minecraft: 1.20.6forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.20.5)
1.3.0-snapshot.5+1.20.5-forge10 авг. 2026 г.2Minecraft: 1.20.5forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.20.4)
1.3.0-snapshot.5+1.20.4-forge10 авг. 2026 г.3Minecraft: 1.20.4forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.20.3)
1.3.0-snapshot.5+1.20.3-forge10 авг. 2026 г.2Minecraft: 1.20.3forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.20.2)
1.3.0-snapshot.5+1.20.2-forge10 авг. 2026 г.1Minecraft: 1.20.2forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Forge 1.20.1)
1.3.0-snapshot.5+1.20.1-forge10 авг. 2026 г.42Minecraft: 1.20.1forge
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 26.2)
1.3.0-snapshot.5+26.2-fabric10 авг. 2026 г.86Minecraft: 26.2fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 26.1)
1.3.0-snapshot.5+26.1-fabric10 авг. 2026 г.1Minecraft: 26.1fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 26.1.2)
1.3.0-snapshot.5+26.1.2-fabric10 авг. 2026 г.28Minecraft: 26.1.2fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 26.1.1)
1.3.0-snapshot.5+26.1.1-fabric10 авг. 2026 г.2Minecraft: 26.1.1fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.21)
1.3.0-snapshot.5+1.21-fabric10 авг. 2026 г.1Minecraft: 1.21fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.21.9)
1.3.0-snapshot.5+1.21.9-fabric10 авг. 2026 г.0Minecraft: 1.21.9fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.21.8)
1.3.0-snapshot.5+1.21.8-fabric10 авг. 2026 г.1Minecraft: 1.21.8fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.21.7)
1.3.0-snapshot.5+1.21.7-fabric10 авг. 2026 г.4Minecraft: 1.21.7fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.21.6)
1.3.0-snapshot.5+1.21.6-fabric10 авг. 2026 г.1Minecraft: 1.21.6fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.21.5)
1.3.0-snapshot.5+1.21.5-fabric10 авг. 2026 г.7Minecraft: 1.21.5fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.21.4)
1.3.0-snapshot.5+1.21.4-fabric10 авг. 2026 г.11Minecraft: 1.21.4fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.21.3)
1.3.0-snapshot.5+1.21.3-fabric10 авг. 2026 г.0Minecraft: 1.21.3fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.21.2)
1.3.0-snapshot.5+1.21.2-fabric10 авг. 2026 г.1Minecraft: 1.21.2fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.21.11)
1.3.0-snapshot.5+1.21.11-fabric10 авг. 2026 г.26Minecraft: 1.21.11fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.21.10)
1.3.0-snapshot.5+1.21.10-fabric10 авг. 2026 г.4Minecraft: 1.21.10fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.21.1)
1.3.0-snapshot.5+1.21.1-fabric10 авг. 2026 г.16Minecraft: 1.21.1fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.20)
1.3.0-snapshot.5+1.20-fabric10 авг. 2026 г.4Minecraft: 1.20fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.20.6)
1.3.0-snapshot.5+1.20.6-fabric10 авг. 2026 г.1Minecraft: 1.20.6fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.20.5)
1.3.0-snapshot.5+1.20.5-fabric10 авг. 2026 г.3Minecraft: 1.20.5fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.20.4)
1.3.0-snapshot.5+1.20.4-fabric10 авг. 2026 г.2Minecraft: 1.20.4fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.20.3)
1.3.0-snapshot.5+1.20.3-fabric10 авг. 2026 г.2Minecraft: 1.20.3fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.20.2)
1.3.0-snapshot.5+1.20.2-fabric10 авг. 2026 г.1Minecraft: 1.20.2fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.5 (Fabric 1.20.1)
1.3.0-snapshot.5+1.20.1-fabric10 авг. 2026 г.8Minecraft: 1.20.1fabric
Что нового
**Fifth snapshot of the 1.3.0 line (in development).** Entries land here as they are built.
### Fixed
- **Every frame-rebuilding transform path can now read the class hierarchy it is rewriting.** The loader passes and normal AOT compiler already supplied the outer mod's classes, but the standalone CLI, full AOT worker pool, Fabric jar-in-jar pass, CLI nested-jar pass, and AOT nested-jar pass did not. A branch between two classes owned by the mod or bundled library could therefore be widened to `Object` and cached or written into a jar that the JVM rejects. Nested transforms now use a thread-local hierarchy scope, so a library sees its own classes while recursive libraries and parallel outer transforms retain the correct provider. Tested with direct CLI, full AOT, normal AOT, and both CLI and AOT nested-jar frame regressions, including a holder class written before its parent in the archive.
- **Patchouli 1.20.1 reaches the stable client phase on Fabric 1.21.11.** Patchouli crossed several unrelated API changes at once: smithing-recipe accessors now return `Optional`, its inventory mixin targeted a deleted screen superclass, a `SoundEvent` accessor became a record component, item properties require an id before construction, and Minecraft no longer exposes the nested `Identifier.Serializer` used by two Gson builders. Retromod now repairs those bytecode shapes and keeps Fabric API's native resource-loader v0 compatibility instead of redirecting it to the newer v1 API. Three Patchouli features whose host APIs were structurally replaced are disabled safely: ghost multiblock rendering uses the normal buffer source, resource-pack-provided guide books are not discovered, and the custom animated book model and completion predicate are inactive. Built-in books and normal guide-book rendering remain available. Tested with focused transform regressions and the exact Patchouli 1.20.1-85 Fabric jar on a 1.21.11 client through resource reload, shader linking, sound startup, and texture-atlas creation, with no new crash report.
- **Constructor transforms no longer loop over unrelated overloads.** Registering a factory for one removed constructor caused every other constructor on that class to be deferred and spilled through temporary locals on every transform pass. Mods that created many identifiers or another partly redirected type accumulated redundant bytecode until Retromod hit its five-pass safety cap, even though no redirect cycle existed. Retromod now checks that a class actually calls a redirected constructor, applies constructor factories only on the first pass, and ignores identity method redirects that can never change a call. Reproduced across 18 Arcanus classes that previously hit the cap; the same exact jar now transforms with zero cap warnings. Tested with focused iteration and unmatched-overload regressions.
- **ENGRAM 0.8 starts on Fabric 1.21.11 (#179).** The 1.20.1 mod crossed several independent changes: `EntityType.Builder.build` now takes a registry key, player model-part data moved from `Player` to `Avatar`, attributes became holders, raw channel networking became typed payload networking, and world render events moved into Fabric API's `world` package. Retromod now translates each of those shapes, including accessor mixins whose target field moved to a new owner. The client networking bridge works in both intermediary and Mojang runtime namespaces and registers raw-byte codecs for the mod's old channels. Pre-26.1 transforms also preserve precise source stack frames for frame-neutral rewrites, preventing an `Entity` and `ClientPlayer` branch from being widened to `Object` and rejected by the verifier. Fabric prelaunch host probes now read class resources without defining Minecraft classes, so checking a member cannot trigger Mixin before the mod has been transformed. Tested with focused JUnit coverage, a Fabric test-mod accessor that passed in-game, the exact ENGRAM 0.8.0-beta jar, and a clean 1.21.11 client launch through resource reload, sound startup, and atlas creation. ENGRAM's optional offscreen screenshot helper still logs a soft warning because 1.21.11 removed the `GameRenderer.renderHand` field it accesses; normal startup and the rest of the mod remain available.
- **Old 2D Items reaches the title screen on Fabric 26.2 (#190).** Its 1.21.11 build creates a custom item pipeline with four builder methods removed in 26.2, reads the renamed `DefaultVertexFormat.NEW_ENTITY` field, and pairs a custom vertex shader with a vanilla fragment shader that no longer exists. Retromod now embeds a per-mod builder bridge for named samplers, blend state, vertex layout, fragment selection, and final pipeline construction; redirects `NEW_ENTITY` to `ENTITY`; and updates the old vertex shader outputs for 26.2's consolidated item fragment stage. Tested with `LegacyRenderPipelineBuilderBridgeTest`, `ModDataMigratorTest`, a Fabric test-mod load check, the exact Old 2D Items 1.0.2 jar, and a stable 26.2 client launch through shader compilation, sound startup, and atlas creation.
- **Pathmind initializes and reaches the title screen on Fabric 26.2 (#208).** Fabric API renamed the outer tick-event fields from `START_WORLD_TICK` and `END_WORLD_TICK` to `START_LEVEL_TICK` and `END_LEVEL_TICK` along with the callback interfaces. It also renamed the client `C2SConfigurationChannelEvents` and `C2SPlayChannelEvents` holders and callbacks to `Serverbound...`. Retromod already moved some nested tick types and the older common-package networking spelling, but Pathmind's field reads and client-package channel events survived. Client and server tick fields now move with their callbacks, and both client C2S event families move as a unit. Pathmind also exposes a custom title-screen button that implements the old `renderContents` contract. Offline transforms now add the required `extractContents` method and delegate it to the old renderer, matching the loader transforms instead of crashing on the first title-screen frame. Tested with `FabricTickEventFieldRenameTest`, `FabricNetworkingChannelEventRenameTest`, `LegacyAbstractButtonBridgeTest`, load-time registrations in the Fabric test mod, the exact Pathmind 1.1.5 jar, and a 26.2 client launch.
- **Caelum's client reload listener translates to NeoForge 26.2 (#188).** `RegisterClientReloadListenersEvent` became `AddClientReloadListenersEvent`, and its one-argument `registerReloadListener` call became the id-requiring `addListener`. Retromod now moves the handler parameter and routes the call through the existing id-synthesizing reload bridge. The offline transform also registers and relocates that compiled bridge into the mod jar instead of leaving a Retromod-only call owner behind. Exact NeoForge Minecraft ranges such as Caelum's `[1.21.1]` are widened too. Tested with `NeoForgeReloadListenerBridgeTest`, `NeoForgeExactRangeTest`, the NeoForge test-mod listener, and the exact Caelum 2.0.0.0 jar.
- **#180 (AutoClicky): a mod that branches between two of its own screens starts again.** Wrapping a mod's entrypoint in a try-catch rebuilds that method's stack frames, and that rebuild could not see the mod's own class hierarchy, so a branch between two of the mod's screens was typed as `Object`. The JVM then rejected the method the moment that value reached `setScreen`, and the game died before the title screen with a `VerifyError`. The rebuild now reads the hierarchy out of the jar it is unpacking, which is where the answer already was. Retromod could already do this in its main transform pass; the entrypoint wrapper is a separate pass that had been left out. Tested with `AutoClickyFrameMergeTest` against the reported jar, and with `CommonSuperViaBytesTest` for the resolution itself.
- **The same crash was waiting on NeoForge, Forge, and the precompiler, and is fixed in all three.** None of them offered the mod's own jar to read from, so a mod that branches between two of its own types produced exactly the frame that killed AutoClicky on Fabric. None had been reported, and all three reproduce on demand: with the repair removed, each still yields `Object`. The precompiler mattered most, because it writes its result to a cache and would have kept serving the broken class across restarts. Tested with `ForgeFrameMergeTest` and `AotFrameMergeTest`.
- **Rebuilt frames no longer fall straight to `Object` everywhere else either.** The shared class writer used by the mixin repairs, the 2D GUI migration, and both precompilers answered every unresolvable merge with `Object`. That is safe for the merge but not for what happens next, and it also threw away the exception-merge handling those paths were supposed to have. They now go through the same resolver as the main pass: read the jar when it is available, otherwise keep the existing guess.
- **A Forge mod that listens for tick events constructs again on NeoForge (#184, S33R More Food).** NeoForge split Forge's one fire-twice tick event into separate `Pre` and `Post` events and made the shared parent abstract, and it also removed the `phase` field and the `Phase` enum the listener reads. A Forge listener on the parent no longer registered ("Cannot register listeners for abstract class"), which killed the whole mod at construction. Retromod now points each tick listener at the concrete `Post` event, supplies the removed `Phase` through a small embedded class, and answers the `phase` read with `END`, so the mod's own `phase == END` check stays true and its end-of-tick work runs. `PlayerTickEvent.player`, also removed, now reads through `getEntity()`. This covers the end-of-tick pattern that MCreator generates and that countless mods share. Honest limitation: a listener that specifically wanted the pre-tick (`START`) phase is bound to `Post` too, so its pre-tick branch no longer runs; that is strictly better than the mod failing to load, but it is not a faithful translation of a `START` listener. Tested with `TickEventSplitTest`, and verified in game on a 26.2 NeoForge client: the mod's construction now runs past tick-listener registration, where the abstract-listener error is gone entirely.
- **Old Forge jars without a Minecraft dependency are transformed again (#192, YUNG's Better Portals).** Some Forge 1.16 jars put the Minecraft version only at the start of their own version and list API dependencies after the main `[[mods]]` block. Retromod's lightweight TOML reader let the last dependency overwrite the main mod id, then had no source Minecraft version to build a shim chain from. It now reads identity from the first `[[mods]]` block and accepts a supported Minecraft version followed by `-`, `_`, or `+` as a conservative fallback. Forge patch targets such as 1.20.1 also resolve through their 1.20 shim milestone instead of returning an empty chain. The reported jar now receives all 11 applicable Forge shims and its missing `FlowingFluidBlock` reference moves to `LiquidBlock`. Tested with `ForgeMissingMinecraftMetadataTest`, `ShimRegistryForgeMilestoneTest`, `ForgeFlowingFluidBlockMoveTest`, and the exact Better Portals 0.3.9 jar.
- **Offline Forge transforms apply SRG member mappings when the target uses the unobfuscated 26.x namespace.** The loader entry points already registered these mappings, but `transform` and `batch` did not, so an old Forge jar could keep `func_` and `field_` references after its classes had moved. Pre-26 targets deliberately keep their SRG names because those runtimes do not expose Mojang member names. Tested with `RetromodCliAuxRedirectsTest`.
### Known limitations
Carried over from snapshot.4, each reproduced on a real client:
- **A worldgen registrar reached through an accessor interface.** The invoker and the mod's refmap have to move at the same time.
- **S33R More Food still does not fully load (#184).** The tick-event blocker above is cleared, but the mod is built for 1.19.2, and its next construction step calls the removed `CreativeModeTab(String)` constructor. Creative tabs were reworked into a registry-based builder in 1.19.3, so a faithful translation is a separate, larger migration. The mod now fails later and for a different reason, not on tick events.
- **YUNG's Better Portals still does not fully load on Forge 1.20.1 (#192).** The reported missing class and the skipped shim chain are fixed, but the next construction step reaches Forge's cross-generation SRG problem: a 1.16 `func_` member needs the target runtime's newer `m_` name. Mapping it to the Mojang name is only correct for unobfuscated 26.x hosts. A general pre-26 Forge solution needs an owner-and-descriptor-aware old-SRG-to-target-SRG mapping pass, not a guessed global rename.
### Infrastructure
- **Release version strings now have one Java source of truth.** The CLI banner, AOT cache version, and Fabric presence packet now read `RetromodVersion.RETROMOD_VERSION` instead of carrying three independent literals that could drift during a release bump. A release-consistency test checks the POM, build script, README badge, changelog header, runtime constants, and rejects additional Java copies of the current version. The build-matrix documentation now also reflects Forge 26.2 output.
- **`build-all.sh` no longer builds every distribution jar twice.** Its Maven step now skips the exec plugin, which otherwise called `build-all.sh --skip-build` inside the outer build before the outer script repeated the same 67-jar phase.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 26.2)
1.3.0-snapshot.4+26.2-neoforge3 авг. 2026 г.63Minecraft: 26.2neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 26.1.2)
1.3.0-snapshot.4+26.1.2-neoforge3 авг. 2026 г.26Minecraft: 26.1.2neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.21)
1.3.0-snapshot.4+1.21-neoforge3 авг. 2026 г.8Minecraft: 1.21neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.21.9)
1.3.0-snapshot.4+1.21.9-neoforge3 авг. 2026 г.10Minecraft: 1.21.9neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.21.8)
1.3.0-snapshot.4+1.21.8-neoforge3 авг. 2026 г.0Minecraft: 1.21.8neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.21.7)
1.3.0-snapshot.4+1.21.7-neoforge3 авг. 2026 г.2Minecraft: 1.21.7neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.21.6)
1.3.0-snapshot.4+1.21.6-neoforge3 авг. 2026 г.2Minecraft: 1.21.6neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.21.5)
1.3.0-snapshot.4+1.21.5-neoforge3 авг. 2026 г.5Minecraft: 1.21.5neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.21.4)
1.3.0-snapshot.4+1.21.4-neoforge3 авг. 2026 г.2Minecraft: 1.21.4neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.21.3)
1.3.0-snapshot.4+1.21.3-neoforge3 авг. 2026 г.3Minecraft: 1.21.3neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.21.2)
1.3.0-snapshot.4+1.21.2-neoforge3 авг. 2026 г.2Minecraft: 1.21.2neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.21.11)
1.3.0-snapshot.4+1.21.11-nf3 авг. 2026 г.29Minecraft: 1.21.11neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.21.10)
1.3.0-snapshot.4+1.21.10-nf3 авг. 2026 г.2Minecraft: 1.21.10neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.21.1)
1.3.0-snapshot.4+1.21.1-neoforge3 авг. 2026 г.154Minecraft: 1.21.1neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.20.6)
1.3.0-snapshot.4+1.20.6-neoforge3 авг. 2026 г.0Minecraft: 1.20.6neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.20.5)
1.3.0-snapshot.4+1.20.5-neoforge3 авг. 2026 г.1Minecraft: 1.20.5neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.20.4)
1.3.0-snapshot.4+1.20.4-neoforge3 авг. 2026 г.0Minecraft: 1.20.4neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.20.3)
1.3.0-snapshot.4+1.20.3-neoforge3 авг. 2026 г.2Minecraft: 1.20.3neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.20.2)
1.3.0-snapshot.4+1.20.2-neoforge3 авг. 2026 г.1Minecraft: 1.20.2neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (NeoForge 1.20.1)
1.3.0-snapshot.4+1.20.1-neoforge3 авг. 2026 г.4Minecraft: 1.20.1neoforge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 26.2)
1.3.0-snapshot.4+26.2-forge3 авг. 2026 г.25Minecraft: 26.2forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 26.1)
1.3.0-snapshot.4+26.1-forge3 авг. 2026 г.5Minecraft: 26.1forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 26.1.2)
1.3.0-snapshot.4+26.1.2-forge3 авг. 2026 г.13Minecraft: 26.1.2forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 26.1.1)
1.3.0-snapshot.4+26.1.1-forge3 авг. 2026 г.1Minecraft: 26.1.1forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.21)
1.3.0-snapshot.4+1.21-forge3 авг. 2026 г.0Minecraft: 1.21forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.21.9)
1.3.0-snapshot.4+1.21.9-forge3 авг. 2026 г.1Minecraft: 1.21.9forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.21.8)
1.3.0-snapshot.4+1.21.8-forge3 авг. 2026 г.1Minecraft: 1.21.8forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.21.7)
1.3.0-snapshot.4+1.21.7-forge3 авг. 2026 г.3Minecraft: 1.21.7forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.21.6)
1.3.0-snapshot.4+1.21.6-forge3 авг. 2026 г.0Minecraft: 1.21.6forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.21.5)
1.3.0-snapshot.4+1.21.5-forge3 авг. 2026 г.7Minecraft: 1.21.5forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.21.4)
1.3.0-snapshot.4+1.21.4-forge3 авг. 2026 г.1Minecraft: 1.21.4forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.21.3)
1.3.0-snapshot.4+1.21.3-forge3 авг. 2026 г.2Minecraft: 1.21.3forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.21.2)
1.3.0-snapshot.4+1.21.2-forge3 авг. 2026 г.0Minecraft: 1.21.2forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.21.11)
1.3.0-snapshot.4+1.21.11-forge3 авг. 2026 г.1Minecraft: 1.21.11forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.21.10)
1.3.0-snapshot.4+1.21.10-forge3 авг. 2026 г.2Minecraft: 1.21.10forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.21.1)
1.3.0-snapshot.4+1.21.1-forge3 авг. 2026 г.12Minecraft: 1.21.1forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.20)
1.3.0-snapshot.4+1.20-forge3 авг. 2026 г.5Minecraft: 1.20forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.20.6)
1.3.0-snapshot.4+1.20.6-forge3 авг. 2026 г.7Minecraft: 1.20.6forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.20.5)
1.3.0-snapshot.4+1.20.5-forge3 авг. 2026 г.1Minecraft: 1.20.5forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.20.4)
1.3.0-snapshot.4+1.20.4-forge3 авг. 2026 г.3Minecraft: 1.20.4forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.20.3)
1.3.0-snapshot.4+1.20.3-forge3 авг. 2026 г.1Minecraft: 1.20.3forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.20.2)
1.3.0-snapshot.4+1.20.2-forge3 авг. 2026 г.3Minecraft: 1.20.2forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Forge 1.20.1)
1.3.0-snapshot.4+1.20.1-forge3 авг. 2026 г.150Minecraft: 1.20.1forge
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 26.2)
1.3.0-snapshot.4+26.2-fabric3 авг. 2026 г.254Minecraft: 26.2fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 26.1)
1.3.0-snapshot.4+26.1-fabric3 авг. 2026 г.3Minecraft: 26.1fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 26.1.2)
1.3.0-snapshot.4+26.1.2-fabric3 авг. 2026 г.116Minecraft: 26.1.2fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 26.1.1)
1.3.0-snapshot.4+26.1.1-fabric3 авг. 2026 г.2Minecraft: 26.1.1fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.21)
1.3.0-snapshot.4+1.21-fabric3 авг. 2026 г.12Minecraft: 1.21fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.21.9)
1.3.0-snapshot.4+1.21.9-fabric3 авг. 2026 г.1Minecraft: 1.21.9fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.21.8)
1.3.0-snapshot.4+1.21.8-fabric3 авг. 2026 г.13Minecraft: 1.21.8fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.21.7)
1.3.0-snapshot.4+1.21.7-fabric3 авг. 2026 г.0Minecraft: 1.21.7fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.21.6)
1.3.0-snapshot.4+1.21.6-fabric3 авг. 2026 г.1Minecraft: 1.21.6fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.21.5)
1.3.0-snapshot.4+1.21.5-fabric3 авг. 2026 г.9Minecraft: 1.21.5fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.21.4)
1.3.0-snapshot.4+1.21.4-fabric3 авг. 2026 г.19Minecraft: 1.21.4fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.21.3)
1.3.0-snapshot.4+1.21.3-fabric3 авг. 2026 г.1Minecraft: 1.21.3fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.21.2)
1.3.0-snapshot.4+1.21.2-fabric3 авг. 2026 г.1Minecraft: 1.21.2fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.21.11)
1.3.0-snapshot.4+1.21.11-fabric3 авг. 2026 г.61Minecraft: 1.21.11fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.21.10)
1.3.0-snapshot.4+1.21.10-fabric3 авг. 2026 г.14Minecraft: 1.21.10fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.21.1)
1.3.0-snapshot.4+1.21.1-fabric3 авг. 2026 г.31Minecraft: 1.21.1fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.20)
1.3.0-snapshot.4+1.20-fabric3 авг. 2026 г.1Minecraft: 1.20fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.20.6)
1.3.0-snapshot.4+1.20.6-fabric3 авг. 2026 г.3Minecraft: 1.20.6fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.20.5)
1.3.0-snapshot.4+1.20.5-fabric3 авг. 2026 г.1Minecraft: 1.20.5fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.20.4)
1.3.0-snapshot.4+1.20.4-fabric3 авг. 2026 г.9Minecraft: 1.20.4fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.20.3)
1.3.0-snapshot.4+1.20.3-fabric3 авг. 2026 г.2Minecraft: 1.20.3fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.20.2)
1.3.0-snapshot.4+1.20.2-fabric3 авг. 2026 г.1Minecraft: 1.20.2fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
beta
Retromod 1.3.0-snapshot.4 (Fabric 1.20.1)
1.3.0-snapshot.4+1.20.1-fabric3 авг. 2026 г.23Minecraft: 1.20.1fabric
Что нового
**Fourth snapshot of the 1.3.0 mixin-translation line.** This build collects the compatibility, mapping, loader, and publishing fixes completed after snapshot.3, adds four more real mixin member bridges, and closes several paths where mixin repairs were being skipped entirely. Three reported mods were run on a real client while preparing it, which is where most of the entries below came from.
### Added
- **Legacy MobEffect mixin shadows now survive the Holder migration (#168, Species).** A mixin that shadows `LivingEntity.hasEffect(MobEffect)` no longer fails when the host only exposes `hasEffect(Holder<MobEffect>)`. Retromod replaces the stale abstract shadow with a mixin-owned overload that wraps the old effect through `BuiltInRegistries.MOB_EFFECT`, and applies the same adaptation to related `LivingEntity.getEffect` and `MobEffectInstance.getEffect` calls in that mixin. This preserves mod-owned fields that still store raw `MobEffect` values. Tested with `MixinLegacyMemberBridgeTest` and two load-time mixins in the Fabric test mod, and checked against the real Species 2.3 Fabric jar, where the shadow and all three of its call sites come out correct.
- **Legacy custom-Pose constructor mixins now follow the expanded enum constructor (#168, Species).** The old `(String, int)` `@Invoker("<init>")` becomes a concrete mixin-owned overload that supplies the modern numeric id and serialized name. Existing handler call sites keep their old descriptor. Tested with `MixinLegacyMemberBridgeTest` and a load-time Fabric test-mod mixin.
- **Custom worldgen types register again through their legacy mixins (#168, Species).** Tree decorators, trunk placers, foliage placers, root placers, feature sizes, and block state providers all keep a private constructor and a private registrar, so a mod can only reach them through an `@Invoker` mixin, and that invoker still passes the `Codec` the mod was built against. The invoker now becomes a mixin-owned method that converts the codec and calls the current `MapCodec` member, which leaves the mod's own codec fields and call sites alone. A codec built by `RecordCodecBuilder`, which is what mods normally use, is unwrapped back to the exact `MapCodec` it came from. Any other codec is read as a whole map instead of being nested under a new field, so existing worldgen JSON still loads as written. This covers the mixin written as a class, where the bridge is merged into the target and can reach its private members. A mixin written as an accessor interface is left alone, because a bridge there would sit outside the target and could not reach them. Tested with `MixinLegacyMemberBridgeTest` and a load-time mixin in the Fabric test mod.
- **A mod's config screen opens again when its config library moved its own API (#181, Double Hotbar).** Cloth Config's 26.1 build moved `getConfigScreen` and `getGuiRegistry` off `AutoConfig` onto a new `AutoConfigClient`, keeping their signatures. A mod built against an older Cloth still called the old owner, so opening its settings through Mod Menu threw `NoSuchMethodError` while the rest of the mod ran. Both calls now follow the move, and `register` and `getConfigHolder`, which did not move, are left alone. Established by comparing cloth-config 20.0.149 against 26.1.154 and 26.2.155, and confirmed on a 26.2 client. Tested with `ClothConfigAutoConfigClientTest`.
- **A shadow of the removed `Gui.getCameraPlayer` no longer blocks a HUD mixin.** The HUD rework dropped that method, and a shadow that cannot attach makes Mixin refuse the whole class, so a mod's HUD drawing silently stops with no crash to point at. The shadow becomes a mixin-owned method that reads the camera entity from the client, which is what the removed one did, and returns nothing when the camera is not a player. This clears one blocker; a mixin that also shadows other removed `Gui` internals still will not attach. Tested with `MixinLegacyMemberBridgeTest`.
- **Method-level mixin discovery reports.** `scripts/mixin-method-breaks.py` compares scanned selectors with a host jar, follows class moves and inheritance, and ranks missing target methods for review.
### Fixed
- **Precompiled mods keep working mixins.** Both AOT compilers stopped at the class remap, and a mixin selector is annotation text that the remap does not touch. A prepared mod therefore kept selectors naming the version it was built against, such as `method_55665` where the host now has `getDefaultPassengerAttachmentPoint`, and every one of its mixins failed to apply. The AOT paths now run the same mixin repairs as `transform` and `batch`, in the same position after the remap. Checked against a real Fabric mod, whose prepared jar now carries the same 53 mixin selectors the transform path produces, and covered by `AotMixinRepairTest`.
- **Precompiling a mod no longer drops its refmap, access widener, and data fixes.** The AOT path patched mod metadata but skipped three repairs the other paths run: remapping the mixin refmap, remapping an access widener or classTweaker, and normalising datapack JSON. A 26.x host reads the official namespace, so a prepared Fabric mod could be rejected before any of its code ran, and an intermediary access widener fails so early there is no crash report at all. Verified on a real refmap mod, whose prepared jar now carries a byte-identical refmap to the `transform` output, and covered by `AotMixinRepairTest`.
- **A mixin inside a bundled library is repaired like the mod's own.** Mods that ship a library as a nested jar had that library's classes renamed but not its mixins, so a bundled library's mixins could not resolve their targets. All three paths that unpack nested jars now repair them, and the precompiler additionally applies the refmap, access widener, and datapack repairs it was only doing at the top level. Tested with `AotMixinRepairTest`, using a mod carrying a library built against an older version.
- **A mod whose mixin config is named `modid.mixin.json` is repaired again.** The Fabric runtime path learns which classes are mixins by reading the mod's mixin configs, and it recognised fewer config names than the rest of Retromod. A mod using the singular spelling looked like it had no mixins at all, so none of its mixins were repaired. It now uses the same naming rules everywhere. Tested with `FabricMixinConfigDiscoveryTest`.
- **`retromod aot --output <file>` writes where you asked.** The option was accepted and then ignored, so the prepared jar only ever appeared in the internal cache directory.
- **Mixin member repairs now reach Fabric mods at runtime.** The repairs that match a member by its Mojang descriptor, meaning the legacy member bridges above and the removed worldgen shadow demotion, ran before intermediary names were remapped. On the Fabric runtime path they therefore matched nothing, and the mod still failed on the descriptor it was built against. They now run in the same post-remap position on every loader, which is where the offline transform already applied them. Tested with `MixinLegacyMemberBridgeTest` and `MixinShadowFieldDemotionTest`.
- **Bare mixin selectors now follow owner-scoped Mojang method renames.** A selector such as `method = "setPickedItem"` can use a verified rename from the shim table when its mixin declares one target owner. Multi-target mixins remain untouched when the rename would be ambiguous.
- **The 26.1 pick-block rename is complete.** Calls and mixin selectors for `Inventory.setPickedItem(ItemStack)` now target `addAndPickItem(ItemStack)`.
- **#180 (AutoClicky): frame rebuilding can resolve mod-owned class hierarchies from the jar.** This keeps the real shared superclass instead of widening a merge to `Object` and producing a `VerifyError`.
- **#184 (S33R More Food): the NeoForge `newSimpleChannel` bridge accepts both the old `ResourceLocation` and post-remap `Identifier` spelling.** Confirmed on a 26.2 NeoForge client: the reported `NoSuchMethodError` is gone and the mod is scanned and loaded. It now stops on a different problem, recorded below.
- **The Forge client tick event points at a class that exists.** `TickEvent$ClientTickEvent` was mapped into the `event/tick` package alongside its siblings, but NeoForge keeps the client one with the other client events, so the target never resolved. Verified against neoforge-26.2.0.0-beta.
- **Completed the 26.1 blaze3d `platform` to `opengl` move.** Eleven missed `GlStateManager`, `GlConst`, and `GlDebug` nested classes now redirect to their verified 26.x locations.
- **Added two small 26.2 compatibility repairs.** The misspelled `InstantenousMobEffect` class follows Mojang's corrected `InstantaneousMobEffect` name, and removed `FlyingAnimal` references use an embedded replacement interface.
- **Expanded SRG-to-Mojang coverage through Forge 1.21.8.** The generated union now covers verified client and server mappings across the Forge SRG era, while conflicting IDs are omitted instead of guessed.
### Infrastructure
- **CurseForge uploads include the now-required Client and Server environment versions.** Snapshot uploads can select the conventional beta release type without changing the script.
- **Compatibility reports use one YAML file per report.** Concurrent report pull requests no longer conflict on one shared data file.
### Known limitations
- **Species 2.3 is not yet fully translated.** Its `MobEffect` and `Pose` mixins are repaired, verified against the real 2.3 Fabric jar. Two gaps remain: it reaches the tree decorator registrar through an accessor interface, which the new bridge deliberately skips (below), and `AbstractCriterionTriggerInstance` still needs its separate class and API migration. Snapshot.4 does not claim the full mod loads.
- **A Forge mod that listens for tick events still will not construct on NeoForge (#184).** NeoForge split each tick event into `Pre` and `Post` and made the parent abstract, and it refuses a listener registered on the parent, which fails the mod at construction and takes the whole mod down. Retromod still maps to the parents. Repointing them at `Pre` or `Post` is not enough by itself: a Forge mod also reads the removed `TickEvent.phase` field and compares it against the removed `TickEvent.Phase` enum, so the class split, the field, and the enum have to move together. Reproduced on a 26.2 NeoForge client with S33R More Food.
- **Double Hotbar's second hotbar still does not draw (#181).** Its config screen is fixed and the mod loads cleanly, verified on a 26.2 client. The HUD half is a deeper problem: 26.x moved hotbar drawing out of `Gui` onto a separate `Hud` class, so the mod's `Gui` mixin shadows `renderSlot`, which no longer exists anywhere on that class. Retromod cannot recreate vanilla slot rendering, so this one needs a real port by the mod author. The mixin fails to attach and the rest of the mod keeps working, which is the intended soft failure rather than a crash.
- **A worldgen registrar reached through an accessor interface is not bridged yet.** Two things have to move together and only one is handled today. Mixin widens the private registrar only because of the `@Invoker` a bridge would remove, and the mod's refmap still names that registrar with its old `Codec` descriptor, so retyping the invoker alone would stop it resolving. Mixins written as a class are unaffected.
- **The new mixin work is verified by transform, not yet in game.** Every repair was checked by transforming real mods and reading back the emitted bytecode and stack maps, including the Species jar from the report, and every mixin class across the test corpus still parses afterwards. A launch on a 26.x host is still the confirming step.
release
Retromod 1.3.0-snapshot.3 (NeoForge 26.2)
1.3.0-snapshot.3+26.2-neoforge25 июл. 2026 г.197Minecraft: 26.2neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 26.1.2)
1.3.0-snapshot.3+26.1.2-neoforge25 июл. 2026 г.104Minecraft: 26.1.2neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.21)
1.3.0-snapshot.3+1.21-neoforge25 июл. 2026 г.23Minecraft: 1.21neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.21.9)
1.3.0-snapshot.3+1.21.9-neoforge25 июл. 2026 г.12Minecraft: 1.21.9neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.21.8)
1.3.0-snapshot.3+1.21.8-neoforge25 июл. 2026 г.22Minecraft: 1.21.8neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.21.7)
1.3.0-snapshot.3+1.21.7-neoforge25 июл. 2026 г.12Minecraft: 1.21.7neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.21.6)
1.3.0-snapshot.3+1.21.6-neoforge25 июл. 2026 г.12Minecraft: 1.21.6neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.21.5)
1.3.0-snapshot.3+1.21.5-neoforge25 июл. 2026 г.12Minecraft: 1.21.5neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.21.4)
1.3.0-snapshot.3+1.21.4-neoforge25 июл. 2026 г.25Minecraft: 1.21.4neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.21.3)
1.3.0-snapshot.3+1.21.3-neoforge25 июл. 2026 г.12Minecraft: 1.21.3neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.21.2)
1.3.0-snapshot.3+1.21.2-neoforge25 июл. 2026 г.14Minecraft: 1.21.2neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.21.11)
1.3.0-snapshot.3+1.21.11-nf25 июл. 2026 г.48Minecraft: 1.21.11neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.21.10)
1.3.0-snapshot.3+1.21.10-nf25 июл. 2026 г.34Minecraft: 1.21.10neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.21.1)
1.3.0-snapshot.3+1.21.1-neoforge25 июл. 2026 г.316Minecraft: 1.21.1neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.20.6)
1.3.0-snapshot.3+1.20.6-neoforge25 июл. 2026 г.9Minecraft: 1.20.6neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.20.5)
1.3.0-snapshot.3+1.20.5-neoforge25 июл. 2026 г.14Minecraft: 1.20.5neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.20.4)
1.3.0-snapshot.3+1.20.4-neoforge25 июл. 2026 г.16Minecraft: 1.20.4neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.20.3)
1.3.0-snapshot.3+1.20.3-neoforge25 июл. 2026 г.9Minecraft: 1.20.3neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.20.2)
1.3.0-snapshot.3+1.20.2-neoforge25 июл. 2026 г.13Minecraft: 1.20.2neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (NeoForge 1.20.1)
1.3.0-snapshot.3+1.20.1-neoforge25 июл. 2026 г.35Minecraft: 1.20.1neoforge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 26.2)
1.3.0-snapshot.3+26.2-forge25 июл. 2026 г.127Minecraft: 26.2forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 26.1)
1.3.0-snapshot.3+26.1-forge25 июл. 2026 г.18Minecraft: 26.1forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 26.1.2)
1.3.0-snapshot.3+26.1.2-forge25 июл. 2026 г.23Minecraft: 26.1.2forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 26.1.1)
1.3.0-snapshot.3+26.1.1-forge25 июл. 2026 г.7Minecraft: 26.1.1forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.21)
1.3.0-snapshot.3+1.21-forge25 июл. 2026 г.15Minecraft: 1.21forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.21.9)
1.3.0-snapshot.3+1.21.9-forge25 июл. 2026 г.4Minecraft: 1.21.9forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.21.8)
1.3.0-snapshot.3+1.21.8-forge25 июл. 2026 г.6Minecraft: 1.21.8forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.21.7)
1.3.0-snapshot.3+1.21.7-forge25 июл. 2026 г.4Minecraft: 1.21.7forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.21.6)
1.3.0-snapshot.3+1.21.6-forge25 июл. 2026 г.4Minecraft: 1.21.6forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.21.5)
1.3.0-snapshot.3+1.21.5-forge25 июл. 2026 г.3Minecraft: 1.21.5forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.21.4)
1.3.0-snapshot.3+1.21.4-forge25 июл. 2026 г.9Minecraft: 1.21.4forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.21.3)
1.3.0-snapshot.3+1.21.3-forge25 июл. 2026 г.4Minecraft: 1.21.3forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.21.2)
1.3.0-snapshot.3+1.21.2-forge25 июл. 2026 г.7Minecraft: 1.21.2forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.21.11)
1.3.0-snapshot.3+1.21.11-forge25 июл. 2026 г.30Minecraft: 1.21.11forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.21.10)
1.3.0-snapshot.3+1.21.10-forge25 июл. 2026 г.4Minecraft: 1.21.10forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.21.1)
1.3.0-snapshot.3+1.21.1-forge25 июл. 2026 г.74Minecraft: 1.21.1forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.20)
1.3.0-snapshot.3+1.20-forge25 июл. 2026 г.11Minecraft: 1.20forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.20.6)
1.3.0-snapshot.3+1.20.6-forge25 июл. 2026 г.11Minecraft: 1.20.6forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.20.5)
1.3.0-snapshot.3+1.20.5-forge25 июл. 2026 г.4Minecraft: 1.20.5forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.20.4)
1.3.0-snapshot.3+1.20.4-forge25 июл. 2026 г.9Minecraft: 1.20.4forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.20.3)
1.3.0-snapshot.3+1.20.3-forge25 июл. 2026 г.3Minecraft: 1.20.3forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.20.2)
1.3.0-snapshot.3+1.20.2-forge25 июл. 2026 г.5Minecraft: 1.20.2forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Forge 1.20.1)
1.3.0-snapshot.3+1.20.1-forge25 июл. 2026 г.313Minecraft: 1.20.1forge
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 26.2)
1.3.0-snapshot.3+26.2-fabric25 июл. 2026 г.1,3 тыс.Minecraft: 26.2fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 26.1)
1.3.0-snapshot.3+26.1-fabric25 июл. 2026 г.40Minecraft: 26.1fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 26.1.2)
1.3.0-snapshot.3+26.1.2-fabric25 июл. 2026 г.244Minecraft: 26.1.2fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 26.1.1)
1.3.0-snapshot.3+26.1.1-fabric25 июл. 2026 г.14Minecraft: 26.1.1fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.21)
1.3.0-snapshot.3+1.21-fabric25 июл. 2026 г.13Minecraft: 1.21fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.21.9)
1.3.0-snapshot.3+1.21.9-fabric25 июл. 2026 г.5Minecraft: 1.21.9fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.21.8)
1.3.0-snapshot.3+1.21.8-fabric25 июл. 2026 г.45Minecraft: 1.21.8fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.21.7)
1.3.0-snapshot.3+1.21.7-fabric25 июл. 2026 г.3Minecraft: 1.21.7fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.21.6)
1.3.0-snapshot.3+1.21.6-fabric25 июл. 2026 г.3Minecraft: 1.21.6fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.21.5)
1.3.0-snapshot.3+1.21.5-fabric25 июл. 2026 г.8Minecraft: 1.21.5fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.21.4)
1.3.0-snapshot.3+1.21.4-fabric25 июл. 2026 г.34Minecraft: 1.21.4fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.21.3)
1.3.0-snapshot.3+1.21.3-fabric25 июл. 2026 г.4Minecraft: 1.21.3fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.21.2)
1.3.0-snapshot.3+1.21.2-fabric25 июл. 2026 г.3Minecraft: 1.21.2fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.21.11)
1.3.0-snapshot.3+1.21.11-fabric25 июл. 2026 г.397Minecraft: 1.21.11fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.21.10)
1.3.0-snapshot.3+1.21.10-fabric25 июл. 2026 г.24Minecraft: 1.21.10fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.21.1)
1.3.0-snapshot.3+1.21.1-fabric25 июл. 2026 г.101Minecraft: 1.21.1fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.20)
1.3.0-snapshot.3+1.20-fabric25 июл. 2026 г.4Minecraft: 1.20fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.20.6)
1.3.0-snapshot.3+1.20.6-fabric25 июл. 2026 г.5Minecraft: 1.20.6fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.20.5)
1.3.0-snapshot.3+1.20.5-fabric25 июл. 2026 г.5Minecraft: 1.20.5fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.20.4)
1.3.0-snapshot.3+1.20.4-fabric25 июл. 2026 г.12Minecraft: 1.20.4fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.20.3)
1.3.0-snapshot.3+1.20.3-fabric25 июл. 2026 г.5Minecraft: 1.20.3fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.20.2)
1.3.0-snapshot.3+1.20.2-fabric25 июл. 2026 г.9Minecraft: 1.20.2fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.3 (Fabric 1.20.1)
1.3.0-snapshot.3+1.20.1-fabric25 июл. 2026 г.82Minecraft: 1.20.1fabric
Что нового
**Third snapshot of the 1.3.0 line (in development).** Continues the mixin/transform-compat work; entries land here as they're built. Opens with in-game-found offline-transform parity fixes and a nested-class remapping fix.
**In-game verified on 26.2 Fabric (Prism, 2026-07-24):** snapshot.3 launched to the title screen; AppleSkin 3.0.6, Dynamic FPS 3.11.4, and FastQuit 3.0.0 were transformed from `retromod-input/` (restart prompt shown) and on the second launch ALL THREE loaded with the game alive well past init and zero new crash-reports. That exercises the new `Minecraft.screen` hop (FastQuit carries both rewritten write sites), the `Options.hideGui` bridge and `ON_OSX` bridge (Dynamic FPS / AppleSkin), end to end. One soft-fail residual observed: Dynamic FPS's `MinecraftMixin` `@Shadow private Window handle` no longer resolves (the window field moved on 26.2), so Mixin skipped that one mixin class (the mod still loads; shadow-on-moved-field is a known engine follow-up).
### Fixed
- **The offline `transform` command now applies the full intermediary->Mojang remap when a mod's source MC version can't be read (parity with `batch`/runtime).** `transform` has two paths: version-detected, and an "all shims" fallback taken when the mod's declared MC version is unreadable. The fallback registered the shim chain and polyfills but SKIPPED the 26.1+ vanilla class-moves, `Identifier` ctor redirects, and (for Fabric) the intermediary->Mojang member map that the version-detected path installs. So a distributed Fabric mod that fell through kept its intermediary names (`class_8710`, `class_310`, ...) and crashed on a 26.1+ host (`NoClassDefFoundError`, mixin `@Inject` targets left as `class_XXXX`). Found in-game (AppleSkin, whose version detection fell through). Both paths now share `register26xTargetMappings`; a Fabric mod through the fallback is fully remapped (verified: `class_8710`/all `class_`/`method_` residue -> 0, "member mappings (10178)" applied). Tested (`TransformFallbackParityTest`).
- **Nested intermediary class names now remap whole (`net/minecraft/class_X$class_Y`), no more `Outer$class_Y` hybrid.** The tsv carries the combined entry (`class_327$class_6415 -> Font$DisplayMode`), but the FQ pattern in `remapString`/`remapDescriptor` stopped at `$` and mapped only the outer, leaving a `Font$class_6415` hybrid whose intermediary inner id has no top-level mapping, so it never resolved on 26.x. This bit access-widener and mixin-refmap remapping (found in-game: cloth-config's AW and AppleSkin's refmap carried these) and any descriptor with a nested MC type. A leading nested pattern (`.../class_\d+($class_\d+)+`) now matches and looks up the whole nested name first; anonymous `$1` inners (which keep their index) and plain top-level names are unaffected. Fixes the documented `GlStateManager$class_4535` gap. Tested (`NestedIntermediaryClassRemapTest`).
- **Fabric networking `S2C`/`C2S` -> `Clientbound`/`Serverbound` pattern rename produced the WRONG spelling.** The pattern-heuristic fallback (which fires when a mod's explicit shim table isn't applied, e.g. a mod whose source MC version can't be read) did a naive `name.replace("S2C", "Clientbound")`, turning `playS2C` into `playClientbound`. But 26.1's actual name moves the direction word to the FRONT: `playS2C` -> `clientboundPlay`, `configurationS2C` -> `clientboundConfiguration` (and the `C2S` -> `serverbound...` mirror). So a networking mod that fell through died `NoSuchMethodError: PayloadTypeRegistry.playClientbound()` at init. The rule now reconstructs the real name (`clientbound` + the capitalized channel) and is scoped to networking owners. Found + fixed via an in-game 26.2 Fabric launch of AppleSkin, which now loads to the title screen (with its bundled cloth-config), together with the access-widener/refmap/nested-class/transform-parity fixes above. Tested (`PatternHeuristicsNetworkingTest`).
- **`COMPUTE_FRAMES` no longer over-generalizes a `Throwable`+non-exception merge, fixing a `VerifyError` on any modded try/catch that reuses a local across the try body and its catch.** When ASM recomputes stack-map frames and can't resolve a type (a JiJ/mod class off the transform classpath), it falls back to `commonSuperFallback`. A prior fix (`#94`) made that return `Throwable` whenever EITHER operand was one, which is right for merging two exceptions but WRONG for merging a `Throwable` with a plain non-exception: the real common supertype is `Object`, and typing it `Throwable` makes a predecessor that provides the non-exception fail the frame check. jade's `CommonProxy.lambda$loadComplete$5` (a try body's `goto` and the catch's rethrow both fall into the same trailing `return`, merging `ModMetadata` with `Throwable`) died `VerifyError: Inconsistent stackmap frames` at load. The fallback is now tri-state (provably-Throwable / provably-not / unresolvable): two Throwables merge to `Throwable` (#94 preserved), a Throwable with a provable non-exception is `Object` (jade fixed), and an unresolvable operand is disambiguated by Java's `*Exception`/`*Error` naming convention. Verified in-game (the jade `VerifyError` clears). Tested (`CommonSuperFallbackTest`).
- **`Vec3.<init>(org.joml.Vector3f)` widened to the `Vector3fc` interface (26.x joml concrete->interface modernization), in BOTH the direct-call and the `Vec3::new` method-reference form.** 26.x replaced the concrete-typed `Vec3(Vector3f)` ctor with `Vec3(Vector3fc)` (the interface `Vector3f` implements), so a 1.21.x mod constructing a `Vec3` from a joml vector dies `NoSuchMethodError`. A converting redirect widens the direct `invokespecial` (no cast: `Vector3f` is-a `Vector3fc`). Crucially, the same skew appears as a constructor REFERENCE (`Vec3::new` in a codec `VECTOR3F.map(Vec3::new, ...)`), which compiles to an `invokedynamic` whose impl handle the direct-call redirect never sees; `visitInvokeDynamicInsn` now applies pure-descriptor converting redirects (no spliced conversion, since a method handle has no call site) to those `H_NEWINVOKESPECIAL`/`H_INVOKE*` handles too. jade's `EntityAccessorImpl$SyncData.<clinit>` died on the reference form (reached from `CommonProxy.onInitialize` via a packet `TYPE` static init); both forms now widen. Verified in-game (the jade Vec3 crash clears; its `main` entrypoint fully initializes). Tested (`Corpus26xDescriptorAdaptationTest`).
- **The loader-agnostic 26.1 API adaptations now apply to Fabric mods whose version-graph shim chain is empty (the common 1.21.x case).** The 26.1 common shim (`Common_1_21_11_to_26_1_ClassMoves.register`: class moves, ctor/method renames, the descriptor-signature skews above, client accessor renames, RenderSystem neutralizes) was registered ONLY through the version chain. But `findShimChain(fabric, 1.21.1, 26.x)` returns an EMPTY chain for the most common Fabric mods, so a 1.21.1 Fabric mod got its names remapped (intermediary->Mojang) but NONE of the 26.x signature/API skews, e.g. jade's `Vec3` widening silently didn't apply through `batch`. The offline `batch`/`transform` auxiliary registration now calls `Common.register` unconditionally under the `isUnobfuscatedTarget` gate (idempotent, so double-registering when the chain DOES include the shim is harmless), matching how the class-moves and member mappings are already applied there. Fabric sees these post-remap (Mojang-named), NeoForge/Forge natively. Verified: the Vec3 adaptations, absent before, now fire on a batch-transformed 1.21.1 Fabric mod.
- **`Util.backgroundExecutor()`/`ioPool()`/`nonCriticalIoPool()` return-type change (26.x `TracingExecutor` wrapper).** These returned `java.util.concurrent.ExecutorService` and now return `net.minecraft.TracingExecutor` (a record wrapping the service), so a mod calling them dies `NoSuchMethodError` at init (ModMenu's update checker, found in-game continuing the jade launch). A new `registerReturnUnwrapRedirect` retargets the call to the `TracingExecutor`-returning form and appends `TracingExecutor.service()` to recover the `ExecutorService` the caller expects, i.e. a converting redirect whose return adaptation is a method CALL rather than a single insn (a method handle can't carry it, so the invokedynamic reference path skips unwrap-bearing targets). Verified on a batch-transformed ModMenu (`Util.backgroundExecutor()` -> `TracingExecutor` + `.service()`). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `LazyLoadedValue` now redirects to `java.util.function.Supplier` (Mojang's actual replacement), fixing a mixin `@Accessor` on a vanilla field whose type changed.** Mojang deleted `net.minecraft.util.LazyLoadedValue` and replaced its usages with a plain `Supplier` (e.g. `InputConstants.Key.displayName` is now `Supplier<Component>`). Retromod previously redirected the removed TYPE to an embedded polyfill CLASS, which left a mixin `@Accessor` setter typed `(LazyLoadedValue)`, no longer matching the now-`Supplier` field, so Mixin silently skipped the accessor and the mod died `AbstractMethodError` (jade's `KeyAccess`/`InputConstants$Key.setDisplayName`, found in-game on 26.2 Fabric). The redirect now maps the TYPE to `Supplier` and rewrites `new LazyLoadedValue(supplier)` to the polyfill's static factory `LazyLoadedValue.of(Supplier):Supplier` (a memoizing wrapper that IS-A `Supplier`, so the accessor and its call sites all become `Supplier` consistently, no cross-class ordering); the redirected `.get()` uses `INVOKEINTERFACE` (`Supplier` is now a known interface). The old type's only API was `get()` (== `Supplier.get()`), so nothing is lost. Verified in-game (jade's `KeyAccess` `AbstractMethodError` clears). Tested (`TransformPolyfillRegressionTest`).
- **`KeyMapping` (keybind) constructor bridges for the 26.x category refactor (`RetroKeyMapping`).** The keybind category changed from a `String` translation key to a `KeyMapping.Category` record, so the old `new KeyMapping(name, [type,] code, categoryString)` constructors are gone and ANY mod that adds a keybind dies `NoSuchMethodError` at client init (jade, found in-game on 26.2 Fabric). A constructor-to-factory redirect rewrites both the 4-arg (with an `InputConstants.Type`) and 3-arg (KEYSYM default) forms to `com.retromod.polyfill.minecraft.RetroKeyMapping`, a reflective (Minecraft-free), fail-safe factory that resolves the category string to a `Category` (a vanilla `key.categories.*` string maps to the builtin constant; a mod string registers a cached `retromod:` category; else falls back to `MISC` so the keybind still works) and calls the real constructor. Registered as a synthetic (Forge/NeoForge per-mod embedded, Fabric injected); the redirect appends a `CHECKCAST` since the factory returns `Object`. Verified in-game: jade's keybind construction, which crashed at client init, now clears (jade advances to a further, separate resource-listener ctor skew). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe).
- **`SimpleJsonResourceReloadListener(Gson, String)` bridge for the 1.21.5 resource-reload refactor (synthesized superclass).** The Gson-based constructor was deleted (the class went Codec-based), so a 1.21.x mod that EXTENDS it to load a directory of raw JSON dies `NoSuchMethodError` on the `super(gson, dir)` call at init (jade's `ThemeHelper`, found in-game continuing the 26.2 launch). A superclass rebase repoints such a subclass at a synthesized `RetroSimpleJsonReloadListener` (it `extends` 26.x's `SimplePreparableReloadListener` and re-implements the old scan by delegating `prepare()` to the reflective `RetroReloadScan`: `FileToIdConverter.json(dir).listMatchingResources(rm)` + `GsonHelper.fromJson` -> the `Map<Identifier, JsonElement>` the subclass' `apply(...)` still expects) and rewrites `super(...)` to it. The synthetic declares both the concrete `prepare()Map` (a subclass' `super.prepare()` and its own override are keyed on that concrete return) and the erased `prepare()Object` bridge. Because it is GENERATED (it extends a Minecraft type Retromod can't compile against) it isn't in Retromod's jar, so **synthetic embedding now runs on Fabric too** (previously NeoForge/Forge-only): the generated class travels embedded in the mod jar (reference-gated; no JPMS split-package risk on Fabric). Verified in-game: jade's `ThemeHelper`, which crashed on the super ctor, now loads. Tested (`Corpus26xDescriptorAdaptationTest`: the rebase + super-ctor rewrite + the scan helper's fail-safe).
- **`MinMaxBounds` intermediary-map skew (`criterion` -> `predicates` package move).** The bundled tsv mapped `class_2096`/`$Ints`/`$Doubles` to `net/minecraft/advancements/criterion/MinMaxBounds*`, but 26.x moved the class to `net/minecraft/advancements/predicates/`, so a mod referencing it (jade's allowed-version predicate) died `NoClassDefFoundError` for the stale path. Corrected the three tsv entries (verified: `criterion/MinMaxBounds` absent, `predicates/MinMaxBounds` present on 26.2; jade cleared it in-game). Tested (`NestedIntermediaryClassRemapTest`).
- **`PoseStack.mulPose(Quaternionf)`/`(Matrix4f)` widened to the joml INTERFACE (`Quaternionfc`/`Matrix4fc`).** Same concrete->interface modernization as `Vec3`/`addVertex`: the concrete-typed overloads are gone on 26.x, so a rendering mod calling them dies `NoSuchMethodError`. The value on the stack already implements the interface, so just widen the descriptor (no cast). `PoseStack.mulPose` is ubiquitous in rendering (surfaced by CERBON's Better Beacons, #159). Tested (`Corpus26xDescriptorAdaptationTest`).
- **Removed `RenderSystem` shader setters neutralized (`setShader`/`setShaderColor`/`setShaderTexture`).** The 26.x GpuDevice/RenderPipeline refactor deleted the imperative shader-binding API (shaders moved onto pipeline objects), so a 1.21.x mod calling these dies `NoSuchMethodError` at RENDER time, a game crash when the mod draws. They're now neutralized like the other removed `RenderSystem` state setters (`enableBlend`/`blendFunc`/...): the call is dropped (args popped), turning a render-time crash into a soft-fail (the custom shader/tint/texture bind is lost and rendering may look wrong, but the game doesn't crash). Top-frequency in an 87-mod Fabric 1.21.1 corpus re-audit (`setShader` 26 mods, `setShaderTexture` 18). Tested (`RemovedRenderStateNeutralizeTest`).
- **`Minecraft.screen` bridged to 26.2's `Gui` accessors (the TOP client-structure residual: 31 corpus mods read it, 408 sites, 6 write).** 26.2 moved the public `Minecraft.screen` field to `Gui` (`private Screen screen` with public `screen()`/`setScreen(Screen)`, reached through the public final `Minecraft.gui` field; `Minecraft.setScreenAndShow` is itself just `gui.setScreen` + `renderFrame`; null-during-gameplay semantics preserved; all verified by javap of the real 26.2 vs 26.1-snapshot-10 jars, so this is 26.2-epoch and lives in `Mc26_1To26_2CoreMoves`). A new transformer mechanism, `registerFieldHopAccessor`, rewrites `GETFIELD Minecraft.screen` to `GETFIELD Minecraft.gui` + `INVOKEVIRTUAL Gui.screen()` and `PUTFIELD` to a `SWAP`-spliced `Gui.setScreen(...)`: pure bytecode, no reflection, so per-frame render/input reads cost nothing. Write caveat: the few mods that wrote the field directly to suppress/restore the screen (fastquit, xaerominimap) now go through the real `setScreen`, i.e. modern close/init semantics instead of a silent field swap; net-better than the `NoSuchFieldError` crash they'd otherwise hit. Verified end-to-end: fastquit's read and both write sites transform to the exact hop/`setScreen` splices (javap), zero residual `Minecraft.screen` fieldrefs. Tested (`ClientStructureBridge26xTest`).
- **`Options.hideGui` bridged to 26.2's `Hud` (12 corpus mods read, 1 writes).** 26.2 deleted the public `Options.hideGui` boolean; the F1 state moved to `Hud.isHidden` (private, public `isHidden()`/`toggle()`, NO absolute setter; vanilla reads `mc.gui.hud.isHidden()`, and `Options.keyToggleGui` is now consumed by `Gui.handleKeybinds` -> `hud.toggle()`). The `Options` receiver can't reach that state, so a second new mechanism, `registerFieldStaticBridge` (opcode-aware, unlike the opcode-blind field-to-method form of `registerFieldRedirect`), turns `GETFIELD` into `INVOKESTATIC RetroClientEnv.isHideGui(Object)Z` and `PUTFIELD` into `setHideGui(Object,Z)V`, consuming the receiver as an ignored argument. `RetroClientEnv` (embedded per-mod, reflective, MC-free, public-members-only so NeoForge JPMS stays happy) caches the `Hud` instance (`Minecraft.gui`/`Gui.hud` are public final, so it is process-stable) and expresses a write as the conditional toggle `if (isHidden() != v) toggle()`, the only mutation 26.2 offers. Fail-safe: no client (dedicated server, tests) reads false / no-ops; a too-early call does not latch and is retried. Verified end-to-end on dynamic-fps (both classes bridge, zero residual `hideGui` refs, per-mod embedded copy present). Tested (`ClientStructureBridge26xTest`).
- **`Minecraft.ON_OSX` supplied by an embedded polyfill (10 corpus mods, 45 sites, all reads).** Removed at 26.1 (already absent on 26.1-snapshot-10, so this one lives in the 1.21.11->26.1 common shim); the successor `InputQuirks.ON_OSX` is `private static final`, unreachable by any field redirect. The `GETSTATIC` becomes `INVOKESTATIC RetroClientEnv.isOsx()Z`, which recomputes the value the way vanilla's `Util.getPlatform()` does (`os.name` contains "mac"). Corpus usage is framebuffer flip-Y quirks and Cmd-vs-Ctrl key logic, GETSTATIC-only, so the opcode-blind field-to-method redirect is safe here. Verified end-to-end on AppleSkin (`KeyHelper` bridges, zero residual `ON_OSX` refs). Tested (`ClientStructureBridge26xTest`).
- **The 26.2 Gui->Hud member family bridged: all 17 moved members, including `getChat()`.** 26.2 moved 17 public `Gui` members onto the new `Hud` (reached via the public final `Gui.hud`): `getChat` (how a client mod prints chat messages), `getFont`, `getTabList`, `getBossOverlay`, `getDebugOverlay`, `getSpectatorGui`, the whole title API (`setTitle`/`setSubtitle`/`setTimes`/`clearTitles`/`resetTitleTimes`), `setOverlayMessage`, `setNowPlaying`, `getGuiTicks`, `clearCache`, `onDisconnected` (full 26.1-snapshot-10 vs 26.2 public-method diff; every descriptor identical, only the owner changed). A generated per-mod-embeddable forwarder (`GuiToHudHop`: `static ret m(Gui g, args...) { return g.hud.m(args...); }`) takes the receiver as arg 0, so the transformer's auto-devirtualize rewrites `INVOKEVIRTUAL Gui.m(...)` to `INVOKESTATIC` with the stack untouched; that shape is also handler-preserving for `@Redirect`/`@WrapOperation`/`@WrapWithCondition` mixins (a virtual call's mirrored handler args (receiver, args...) equal the static form's (args...)), and the mixin `@At`-target rewrite composes the receiver-prepended descriptor so selectors keep matching. `getMobEffectSprite` (static on both sides) is a plain owner move. This retires the earlier scan backlog's `setOverlayMessage` handler-mirror hazard. Tested (`ClientStructureBridge26xTest`).
- **33 tag constants deleted at 26.2 recovered via `BlockItemTags` accessors.** 26.2 introduced `BlockItemTags` (paired block+item tag ids) and deleted the corresponding per-registry constants: 17 from `BlockTags` (the wood-type `*_LOGS` set, `LOGS_THAT_BURN`, `SAPLINGS`, the `*_ORES` set, `SMELTS_TO_GLASS`, ...) and 16 from `ItemTags` (`DOORS`, `SLABS`, `STAIRS`, `FLOWERS`, `CHAINS`, `LANTERNS`, ...), enumerated by a full 26.1-snapshot-10 vs 26.2 constant diff. A deleted `GETSTATIC` becomes `GETSTATIC BlockItemTags.X` + `INVOKEVIRTUAL BlockItemTagId.block()`/`.item()` via the existing static-field-accessor mechanism (the `ColorCollection` precedent). Corpus-scan hit: Darker Depths' `RotatedPillarBlockMixin` reads `BlockTags.LOGS_THAT_BURN` in its handler body. Tested (`ClientStructureBridge26xTest`).
- **`LightTexture` bridged (9 corpus mods; 26.1 render rewrite).** Deleted at 26.1; ground truth (workflow-verified on both jars): the static coord math moved WHOLESALE to `util/LightCoordsUtil` (`pack(II)I`/`block(I)I`/`sky(I)I`, identical names/descriptors/bit layout), texture management went to `renderer/Lightmap`, which kept static `getBrightness(DimensionType,I)F` with the same descriptor. Fix: class move `LightTexture` -> `Lightmap` (heals `getBrightness` by itself) + method redirects for the coord statics + `GameRenderer.lightTexture()` bridged to a reflective helper returning the private `Lightmap` instance (receiver-as-arg0; null fail-safe) + `turnOn`/`turnOffLightLayer` neutralized (the global texture-unit bind they did no longer exists; their only corpus consumers reach them through `lightTexture()`, so the chain stays type-correct and inert). Tested (`RenderApiBridge26xTest`).
- **`RenderType` class move + the static getter surface bridged (34 corpus mods; 26.1 render rewrite).** `RenderType` moved to `renderer/rendertype/RenderType` on both 26.x jars; the ubiquitous static getters moved to `rendertype/RenderTypes` with a cull-naming FLIP (old `entityCutout` = culled -> `entityCutoutCull`; old `entityCutoutNoCull` -> `entityCutout`), bridged with owner+desc-scoped redirects (`entitySolid`/`entityCutout`x2/`entityTranslucent`x2/`text`); the block-layer getters (`solid`/`cutout`/`cutoutMipped`/`translucent`/`tripwire`, 9-15 mods each) have NO RenderType successor (chunk layers became the `ChunkSectionLayer` enum) and are approximated by the surviving `*MovingBlock` tokens, vanilla's own layer-to-RenderType conversion. Documented loss, not bridged: the `CompositeState`/`RenderStateShard` builder world (custom render types; 6 subclassers incl. iris/xaero) is deleted wholesale and needs re-authoring. Tested (`RenderApiBridge26xTest`).
- **`ItemBlockRenderTypes` bridged via live model-layer derivation (12 corpus mods; 26.1 render rewrite).** The static block-to-RenderType table was deleted: the layer decision became per-quad model data (`ChunkSectionLayer {SOLID,CUTOUT,TRANSLUCENT}`). All five statics (`getChunkRenderType`, `getRenderType(BlockState,Z)`, `getMovingBlockRenderType`, `getRenderLayer(FluidState)`, `getRenderType(ItemStack,Z)`) bridge to an embedded reflective polyfill that re-derives a block's layer from its live model the way vanilla's SectionCompiler reads it (`collectParts` -> `getQuads` -> the quad material's `layer()`, handling both the 26.1 `spriteInfo` and 26.2 `materialInfo` shapes) and returns the surviving `RenderTypes.*MovingBlock` tokens, the SAME tokens the RenderType getter redirects above use, so mod-side `==` comparisons stay consistent. Two verified traps encoded: the model-set probe order (`getBlockStateModelSet` FIRST; `getBlockModelSet` exists on both versions with different return types) and the 26.1 fluid table being resource-pack-dependent (read live via the liquid-renderer chain, never replicated). Per-state results cache in a map keyed weakly on the model-set instance, so a resource reload naturally invalidates. The erased `(Object,Z)` overload collision (BlockState vs ItemStack) is split into distinct target names. Fail-safe: any resolution failure yields the SOLID token (vanilla's own default); worst case is a wrong layer, never a crash. Needs in-game validation on a mod that draws blocks manually. Tested (`RenderApiBridge26xTest`).
- **`PlayerSkin` restructure bridged (7 corpus mods; 26.1).** The record moved `client/resources` -> `world/entity/player` with its `Model` enum promoted to `PlayerModelType` (constants SLIM/WIDE unchanged): class moves. Its texture accessors were renamed AND wrapped: `texture()`/`capeTexture()`/`elytraTexture()` returning `ResourceLocation` became `body()`/`cape()`/`elytra()` returning the `ClientAsset$Texture` INTERFACE; bridged by rename-capable return-unwrap converting redirects appending `texturePath()` , which required making the unwrap mechanism interface-aware (an `INVOKEVIRTUAL` on an interface is an `IncompatibleClassChangeError`; `registerReturnUnwrapRedirect` gained `unwrapItf`). Tested (`ClientStructureBridge26xTest`).
- **`ReceivingLevelScreen` -> `LevelLoadingScreen` (6 corpus mods; 26.1)**: class move; mods mostly `instanceof` it for screen detection.
- **`ItemInteractionResult` (`class_9062`) merged-class bridge (6 corpus mods).** The 1.20.5-1.21.1 sided item-use result was merged back into `InteractionResult` at 1.21.2 and is ABSENT from the 1.21.4-era intermediary tsv (it died before the harvest), so nothing remapped it and mods die `NoClassDefFoundError`. Class-redirected to the merged interface; the unharvested intermediary members become polyfill calls: the enum constants are GETSTATIC field-to-method redirects (105 corpus refs, dominated by `PASS_TO_DEFAULT_BLOCK_INTERACTION` at 58, which 1.21.2 literally renamed `TRY_WITH_EMPTY_HAND`; `CONSUME_PARTIAL`->`CONSUME`; `SKIP_DEFAULT_BLOCK_INTERACTION`->`FAIL`, the closest no-swing chain-stop), `sidedSuccess(Z)` -> plain `SUCCESS` (the sided split is gone, vanilla's own migration), `consumesAction()` -> the surviving interface default via a receiver-as-arg0 reflective helper (a direct redirect would emit INVOKEVIRTUAL against an interface), and `result()` -> identity (post-merge the receiver IS the result). Tested (`RenderApiBridge26xTest`).
- **`GlStateManager$class_4534/4535` hybrid spellings mapped + the 26.2 blend-factor teardown.** Intermediary keeps `com.mojang.blaze3d` OUTER names plain but its inners got `class_N` ids, so distributed Fabric mods literally contain `GlStateManager$class_4535`-style names (10 corpus mods) that neither the intermediary map (keyed `net/minecraft/class_...`) nor the existing inner-move entries matched: mapped to the promoted 26.1 `SourceFactor`/`DestFactor`. Those promoted enums are then DELETED at 26.2: their `GETSTATIC`s become pushed nulls (static-field nuller) feeding the enum `blendFunc`/`blendFuncSeparate` overloads, which are now neutralized in both spellings (the int forms already were), so the whole blend-call chain soft-fails instead of `NoClassDefFoundError`. Tested (`ClientStructureBridge26xTest.blendFactorTeardown26_2`).
- **`TextureSheetParticle` -> `SingleQuadParticle` rebase (6 corpus mods; 26.1 particle rework).** The old sprite-particle base was deleted; the successor exists identically on both 26.x jars with matching fields (`quadSize`/`rCol`/`sprite`/...), the same protected sprite helpers, and a non-abstract `getGroup()`, so simple content-mod particles inherit everything. Superclass rebase + class redirect; the constructors gained a trailing `TextureAtlasSprite`, appended as null by the insert-defaults super-ctor redirect (the sprite is set post-construction via `setSprite`/`SpriteSet`, exactly the old flow); `pickSprite(SpriteSet)` did not survive and bridges receiver-as-arg0 to a reflective helper (random sprite from the set into the protected `sprite` field; JPMS-sealed hosts fail closed to an invisible particle, never a crash). Honest caveat: a subclass that overrode the old `render(VertexConsumer,...)` loads but its override is never called (the contract became `extract(QuadParticleRenderState,...)`): invisible, not crashed. Tested (`RenderApiBridge26xTest`).
- **GUI 2D-transform migration Phase 3: the `double` translate overload and `mulPose` rotation.** `translate(DDD)V` migrates exactly (drop z with `POP2`, then a `D2F`+`DUP_X2`/`POP`/`SWAP` juggle converts y and x; BasicVerifier-proven). `mulPose(Quaternionf[c])` becomes the 2D `rotate(angle)` via the generated `RetroQuat2D.zAngle` (`2*atan2(z,w)`: exact for the pure-Z rotations that are the only meaningful GUI-space case). Both plug into the existing Phase 1 (immediate) AND Phase 2 (stored-slot) dataflow proofs, so the strictly-conservative 3D-safety guarantees carry over unchanged. Tested (`Gui2DTransformMigrationTest`).
- **AOT output now equals the plain transform output: the per-class AOT pass reroutes through the full `RetromodTransformer.transformClass`.** The former `HybridCompiler` primary implemented only class+method redirects, so an AOT jar silently missed the ENTIRE rest of the mechanism set (converting redirects like `Vec3`, singleton/field accessors, invokedynamic handle rewrites, and the new screen/hideGui bridges): a mod that worked through plain `transform` died `NoSuchFieldError` when prepped via `aot`/`batch --aot` (adversarial-review finding). Its JIT-required markers were never consumed at runtime, so nothing is lost by the reroute; the `transformClassSimple` -> JIT -> ship-original fallback chain (the #125/#127 per-class posture) is preserved. The AOT registration block also gained the same 26.1-common + 26.2-core-moves empty-chain parity as the CLI paths. Verified: a standalone `aot` run of dynamic-fps to 26.2 now carries the hideGui bridge and screen hop (previously raw broken fieldrefs).
- **The transform command's all-shims fallback now gates by target version (pitfall-9, offline).** The unreadable-source-version fallback registered EVERY shim including the 26.1->26.2 ones regardless of the CLI target, so on a 26.1 target it applied 26.2-only rewrites (the screen hop's `Gui.screen()`, the criterion class moves, `getMainCamera`) to mods where the ORIGINAL member still exists and worked, turning a working read into `NoSuchMethodError` (adversarial-review finding; latent for the older CoreMoves entries, made acute by the screen hop). The loop now skips shims targeting a newer MC than the CLI target, mirroring the runtime entry points' gate; API-versioned shims (unparseable targets) stay included as before. Tested (`TransformFallbackParityTest.fallbackAllShimsGatedByTarget`).
- **`RetroClientEnv` no longer latches "unresolvable" during the client's own constructor.** 26.2's `Minecraft.<init>` publishes the singleton early (insn ~133) but assigns `gui` much later (insn ~2579), and mod init/mixins provably run inside that window; a hideGui read there saw `mc != null` but `gui == null`, threw, and latched the failure flag, silencing F1 state for the whole session (adversarial-review finding). Null intermediates are now treated like the not-yet-constructed case: return-and-retry, never latch; the latch is reserved for genuinely structural failures. Also fixed alongside (same review): the two new redirect maps were missing from BOTH no-op fast paths (`transformClass`'s early exit and `RetromodClassVisitor.visitMethod`'s wrap gate), so a hop-only or bridge-only registration set was a silent no-op; both gates now include them. Tested (`ClientStructureBridge26xTest.hopOnlyRegistrationTransforms`).
- **Offline 26.2 targets now get the 26.2 core moves at all (the 26.1 empty-chain fix, completed).** The earlier fix registered the 1.21.11->26.1 common shim unconditionally on the offline paths because the version-graph BFS returns an empty chain for 1.21.x sources, but `Mc26_1To26_2CoreMoves` was still chain-only, so a `batch`/`transform` to 26.2 silently missed EVERY 26.2 core move (`EntityType`->`EntityTypes` constants, `setScreen`->`setScreenAndShow`, the new screen/hideGui bridges, all of it); at runtime the loaders register every shim whose target is at or below the host, so only the offline paths were affected. Both CLI registration sites now also pull in the core moves for a 26.2+ target (gated `!mcVersionExceeds("26.2", target)`, idempotent). Tested (`TransformFallbackParityTest.coreMoves262RegisteredFor262Target`).
- **GUI 2D-transform migration: Phases 1 & 2 (immediate + stored-stack `translate`/`scale`), and the whole migration now reaches Fabric.** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` returns the 2D stack), so a 1.21.x mod's `guiGraphics.pose().pushPose()`/`translate(x,y,z)`/`scale(x,y,z)` links against ops the 2D stack doesn't have (20+ mods each in the corpus audit). On top of Phase 0 (the immediate no-arg `pushPose`/`popPose` peephole): **Phase 1** migrates the immediate arg-carrying float ops (`gg.pose().translate(x,y,z)` -> 2D `translate(x,y)`, drop z, pop the fluent result), and **Phase 2** migrates the STORED-stack idiom most GUI code uses (`var p = gg.pose(); p.pushPose(); p.translate(...); p.popPose();`) by retyping the local. Both use an ASM `SourceInterpreter` dataflow and are STRICTLY conservative so 3D world rendering is never corrupted: an op is migrated only when its receiver is proven to be a single `GuiGraphics.pose()` (Phase 1) or a local whose SOLE store is one `pose()` and whose EVERY load feeds a migratable op (Phase 2). A genuine 3D `PoseStack` - a param, a foreign source, a reassigned slot, or a load passed to a method - bails untouched. The migration also now runs POST-remap, so it finally reaches Fabric mods (intermediary-named pre-remap, where the pre-filter and Mojang-keyed matching can't line up); it fired on fancymenu and jade (Phase 2 on 4 and 1 methods). The cross-method pattern (the pose stack passed to a render helper) and the `double`/`mulPose` overloads remain (Phase 3). Verified: 41 migrated corpus classes pass ASM `BasicVerifier` with 0 errors; the "3D `PoseStack` untouched" safety is unit-tested for the immediate, foreign-use, reassigned-slot, and foreign-source cases. Tested (`Gui2DTransformMigrationTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken).** A Forge mod declaring a Maven range whose finite upper bound contains the host (e.g. `[1.19,1.20.1]` on a 1.20.1 host, the shape of `packetfixer`/`CrashAssistant`/`cosmeticcorpsecompat` and the rest of the 16-mod report) was detected as its LOWER bound ("a 1.19 mod"), transformed, and broken alongside everything it dragged down. The detector now resolves a finite range containing the host to the host itself, so `needsTransformation` skips it on every path (runtime in-place scan, retromod-input, CLI batch). OPEN ranges (`[1.19,)`) deliberately keep lower-bound detection: that shape is mod-author optimism, and on a 26.x host such a mod genuinely needs translation, so skipping it would regress the entire old-mod use case. Bracket semantics (inclusive/exclusive) are honored: `[1.19,1.20.1)` on 1.20.1 still transforms. Tested (`ModVersionRangeContainmentTest`, 5 cases including the report shapes).
- **#157 (nekomasfixed): the whole 1.21.5-1.21.11 mapping gap closed, plus four remap-engine bugs it exposed.** The intermediary-to-Mojang table was a 1.21.4-era harvest, so EVERY id Mojang added in 1.21.5 through 1.21.11 was left untranslated; nekomasfixed (a 1.21.11 mod) carried 49 such classes plus 15 nested combos, and the fatal one was `class_11890` = `world/entity/Avatar`, the direct superclass of its target-dummy entity (`NoClassDefFoundError` at Bootstrap). Appended a freshly composed 1.21.11 delta (1802 CLASS + 76 FIELD + 718 METHOD entries; ambiguously-overloaded names skipped rather than guessed) and three 26.2 sub-package moves it surfaced (`AtlasManager` -> `model/sprite/`, `ParticleGroupRenderState`/`CameraRenderState` -> `state/level/`). Chasing the residue then exposed four engine bugs, all fixed and regression-tested: (1) FIELD-pattern `@At` target selectors (`Lowner;name:Ldesc;`) were parsed as method names and left unmapped; (2) `@Slice(from/to=@At(...))` inner nodes were never walked; (3) the sponge injector dispatch was a whitelist that silently skipped `@ModifyArgs` and `@ModifyConstant` (now a family-prefix match like the MixinExtras branch); (4) invokedynamic SAM names were never remapped, so a lambda over an intermediary-named MC interface kept its `method_N` name and died `LambdaConversionException` (a fully general latent bug). End-to-end: the reporter's exact jar now transforms with ZERO intermediary residue, from 64 broken refs. Tested (`MixinSelectorRemapRegressionTest`). In-game verify on a 26.1.2 host still pending.
- **#162 (ENGRAM): `EntityType$Builder.build(String)` bridged on pre-26.1 hosts.** 1.21.2 flipped `method_5905`'s descriptor from `(String)` to `(ResourceKey)`; a 1.20.1-1.21.1 Fabric mod registering an entity dies `NoSuchMethodError` at `<clinit>` on a 1.21.2-1.21.11 intermediary host, where the Mojang-named shims can't reach (#55 family). New host-probing `Pre1_21_2EntityTypeBuildBridge` (registers only when the host's builder takes the key form) rewrites the call receiver-as-arg0 onto an embedded reflective helper that reconstructs the key the way vanilla's own migration did (`ResourceKey.create(Registries.ENTITY_TYPE, Identifier.parse(id))`, all shape-discovered so intermediary id drift can't break it). Tested (`Pre1_21_2EntityTypeBuildBridgeTest`).
- **#174: multi-version mods that run NATIVELY on the host are no longer transformed (and broken)** (entry above under the detector fix).
- **#140 (Cooking for Blockheads 1.12.2 on NeoForge): the legacy `@Mod` upgrade now matches the post-remap annotation spelling.** Pass ordering can remap `Lnet/minecraftforge/fml/common/Mod;` to the NeoForge spelling BEFORE the 1.12.2 value-shape upgrade runs; matching only the Forge desc (and pre-filtering on the Forge string) silently skipped the collapse, so the mod was scanned but never registered. Both spellings now match; the modid-element gate keeps modern value-shaped `@Mod` classes untouched. Tested (`Forge1122LifecycleTest`).
- **#156 (Wonderland / the MCreator 1.20.1 scaffold on NeoForge): the SimpleChannel surface no longer crash-loops, it soft-fails.** The old bare class redirects (`NetworkRegistry` -> `PayloadRegistrar`) were actively harmful: NeoForge's registrar never had `newSimpleChannel`, so every MCreator-style mod died `NoSuchMethodError` in `<clinit>`. The whole surface now routes onto the embedded `NetworkShim` (`newSimpleChannel` descriptor-erased; the `messageBuilder(Class,int,NetworkDirection)` chain incl. the `consumerMainThread` idiom; `registerMessage` incl. the `Optional` overload; `send(PacketTarget,msg)` erased), and the shim's inner classes are now actually LISTED for per-mod embedding (the loaders resolve exact names, no glob). Honest scope: the mod LOADS and registrations are collected, but cross-side packet delivery stays inert until the replay bridge lands (tracked 1.3.0 Forge-to-NeoForge work together with `NetworkHooks`/`ITeleporter`/`LivingTickEvent` from the same family). Tested (`SimpleChannelBridgeTest`).
- **SRG->Mojang table roughly doubled: from a 1.20.1-only join to the union across the whole Forge SRG era (56,332 new entries).** The shipped table was a single 1.20.1 join, so a mod built for an EARLIER version referenced SRG ids for members that 1.20.1 had removed or renamed, and those ids were simply absent, which is why contributors kept finding per-modpack gaps (e.g. #171 harvested ~600 for a 1.18.2 pack by hand). The table now carries the UNION of the identical MCPConfig + Mojang official join across 1.16.5, 1.17.1, 1.18.2, 1.19.2, 1.19.4, 1.20.1, 1.20.4, and 1.20.6 (client+server; overloads disambiguated by obf descriptor; lambda$/access$/`<init>` filtered), taking the table from ~53.7k to ~110k entries (60,000 FIELD + 49,986 METHOD). Safety: an SRG id that maps to DIFFERENT Mojang names across versions is AMBIGUOUS (a renamed/reused member) and is OMITTED, never guessed (148 dropped); adding a mapping for a member later removed is harmless (it was going to `NoSuchXError` on that member regardless, and it fixes every version where the member persists). Verified: 40/40 of a random sample re-derived correctly against an independent join, 0 cross-file duplicate-key conflicts, and the 1.20.1 sentinels preserved. The generator ships as `scripts/harvest-srg-union.py` (reproducible). Also flagged 3 pre-existing shipped-table values the join disagrees with (`f_62138_` `runtimebiomeSource` -> `runtimeBiomeSource`, `m_47831_` `getTextureLocation` -> `build`, `m_142469_` `createRenamer` -> `getBoundingBox`) but did NOT change them (left for maintainer review). Tested (`SrgUnionCoverageTest`).
### Infrastructure
- **Compat-report PRs can no longer conflict with each other: the DB is now one file per entry.** Every auto-generated report PR used to append to the shared `docs/_data/compatdb.yml`, so whenever two reports were open at once, merging the first conflicted the second and the maintainer had to resolve it by hand (GitHub's built-in resolution mangled the YAML and broke the docs build). The DB is now a Jekyll data DIRECTORY, `docs/_data/compatdb/`, with one `issue-<N>.yml` per report (all 27 existing entries migrated, round-trip-validated; format documented in the directory's README, which the Jekyll data loader ignores). The workflow writes each report to its own new file (validated including bundle-entry nesting), and `compatdb.html` collects the directory hash into the same sorted array the page always used, so the UI is unchanged. The 8 then-open compat reports were incorporated directly as per-entry files in this snapshot, so their auto-generated (old-format) PRs are superseded.
### Research (documented, not yet shipped)
- **Render-redesign bridge plans, workflow-verified: the remaining top residuals each have a concrete, adversarially-checked implementation plan** in `scripts/research/render-redesign-bridge-plans.md` (7 families traced against both 26.x jars by paired trace+verify agents). Bridgeable with real work: `BakedModel` (21 mods; per-mod synthesized `LegacyBakedModel` interface over the 26.x `BlockStateModel`/`BlockStateModelPart` pair, per-host-version bodies), `BufferUploader` (13; embedded GpuDevice/RenderPass immediate-draw polyfill), `ItemBlockRenderTypes` (12; reflective per-state layer lookup with the verified model-set probe order), `BlockRenderDispatcher`/`BlockModelShaper` (13/12) and `ModelResourceLocation` (7; synthetic value class + lookup helpers). Genuinely lost (documented): quad-internals mods (sodium/iris/ferritecore; 26.x `BakedQuad` is a different data structure), the FRAPI renderer family, and the `CompositeState`/`RenderStateShard` custom-render-type world.
- **87-mod Fabric 1.21.1 corpus re-audit against this snapshot: the remaining residual breaks are dominated by the 26.x render redesign, which is structural.** Transformed the top ~90 Fabric 1.21.1 mods to 26.2 and link-checked them against the real jar, ranking residual `net.minecraft`/`com.mojang` breaks by mod-frequency. The head of the distribution is almost entirely the blaze3d/GPU render overhaul and its fallout: removed rendering CLASSES (`BakedModel` 25 mods, `BufferUploader` 21, `LightTexture` 19, `ItemBlockRenderTypes` 16, `BlockModelShaper`/`BlockRenderDispatcher`/`ModelResourceLocation`/`RenderType$CompositeState`/`RenderStateShard*`/`DimensionSpecialEffects` 9-14 each) that were DELETED (not moved), the GUI 2D-transform migration (`GuiGraphics` -> `GuiGraphicsExtractor` where `pose()` now returns a `Matrix3x2fStack`, so `pushPose`/`popPose`/`translate`/`scale`/`text` don't line up: 20-27 mods each, the phased RFC's later stages), and client-structure changes (`Minecraft.screen` field 45 mods; `Minecraft.ON_OSX` 26; `Options.hideGui` 20). The render-redesign head of the list is a re-authoring job, not redirects, so it is the genuine "less-supported" surface; the mechanically-fixable tail from the same audit (the `mulPose` widening and the shader-setter neutralize above) shipped in this snapshot. UPDATE: the three client-structure members turned out to be mechanically bridgeable after all (a deeper 26.2 trace found the public `Gui.screen()`/`setScreen` accessors and `Hud.isHidden()`/`toggle()`), and shipped in this snapshot; see the `Minecraft.screen`/`Options.hideGui`/`Minecraft.ON_OSX` entries above.
release
Retromod 1.3.0-snapshot.2 (NeoForge 26.2)
1.3.0-snapshot.2+26.2-neoforge15 июл. 2026 г.118Minecraft: 26.2neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 26.1.2)
1.3.0-snapshot.2+26.1.2-neoforge15 июл. 2026 г.70Minecraft: 26.1.2neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.21)
1.3.0-snapshot.2+1.21-neoforge15 июл. 2026 г.12Minecraft: 1.21neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.21.9)
1.3.0-snapshot.2+1.21.9-neoforge15 июл. 2026 г.5Minecraft: 1.21.9neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.21.8)
1.3.0-snapshot.2+1.21.8-neoforge15 июл. 2026 г.9Minecraft: 1.21.8neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.21.7)
1.3.0-snapshot.2+1.21.7-neoforge15 июл. 2026 г.4Minecraft: 1.21.7neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.21.6)
1.3.0-snapshot.2+1.21.6-neoforge15 июл. 2026 г.3Minecraft: 1.21.6neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.21.5)
1.3.0-snapshot.2+1.21.5-neoforge15 июл. 2026 г.16Minecraft: 1.21.5neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.21.4)
1.3.0-snapshot.2+1.21.4-neoforge15 июл. 2026 г.5Minecraft: 1.21.4neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.21.3)
1.3.0-snapshot.2+1.21.3-neoforge15 июл. 2026 г.3Minecraft: 1.21.3neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.21.2)
1.3.0-snapshot.2+1.21.2-neoforge15 июл. 2026 г.8Minecraft: 1.21.2neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.21.11)
1.3.0-snapshot.2+1.21.11-nf15 июл. 2026 г.33Minecraft: 1.21.11neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.21.10)
1.3.0-snapshot.2+1.21.10-nf15 июл. 2026 г.7Minecraft: 1.21.10neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.21.1)
1.3.0-snapshot.2+1.21.1-neoforge15 июл. 2026 г.179Minecraft: 1.21.1neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.20.6)
1.3.0-snapshot.2+1.20.6-neoforge15 июл. 2026 г.2Minecraft: 1.20.6neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.20.5)
1.3.0-snapshot.2+1.20.5-neoforge15 июл. 2026 г.2Minecraft: 1.20.5neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.20.4)
1.3.0-snapshot.2+1.20.4-neoforge15 июл. 2026 г.3Minecraft: 1.20.4neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.20.3)
1.3.0-snapshot.2+1.20.3-neoforge15 июл. 2026 г.1Minecraft: 1.20.3neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.20.2)
1.3.0-snapshot.2+1.20.2-neoforge15 июл. 2026 г.2Minecraft: 1.20.2neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (NeoForge 1.20.1)
1.3.0-snapshot.2+1.20.1-neoforge15 июл. 2026 г.3Minecraft: 1.20.1neoforge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 26.2)
1.3.0-snapshot.2+26.2-forge15 июл. 2026 г.60Minecraft: 26.2forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 26.1)
1.3.0-snapshot.2+26.1-forge15 июл. 2026 г.11Minecraft: 26.1forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 26.1.2)
1.3.0-snapshot.2+26.1.2-forge15 июл. 2026 г.16Minecraft: 26.1.2forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 26.1.1)
1.3.0-snapshot.2+26.1.1-forge15 июл. 2026 г.1Minecraft: 26.1.1forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.21)
1.3.0-snapshot.2+1.21-forge15 июл. 2026 г.8Minecraft: 1.21forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.21.9)
1.3.0-snapshot.2+1.21.9-forge15 июл. 2026 г.3Minecraft: 1.21.9forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.21.8)
1.3.0-snapshot.2+1.21.8-forge15 июл. 2026 г.2Minecraft: 1.21.8forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.21.7)
1.3.0-snapshot.2+1.21.7-forge15 июл. 2026 г.2Minecraft: 1.21.7forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.21.6)
1.3.0-snapshot.2+1.21.6-forge15 июл. 2026 г.5Minecraft: 1.21.6forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.21.5)
1.3.0-snapshot.2+1.21.5-forge15 июл. 2026 г.2Minecraft: 1.21.5forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.21.4)
1.3.0-snapshot.2+1.21.4-forge15 июл. 2026 г.4Minecraft: 1.21.4forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.21.3)
1.3.0-snapshot.2+1.21.3-forge15 июл. 2026 г.1Minecraft: 1.21.3forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.21.2)
1.3.0-snapshot.2+1.21.2-forge15 июл. 2026 г.1Minecraft: 1.21.2forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.21.11)
1.3.0-snapshot.2+1.21.11-forge15 июл. 2026 г.20Minecraft: 1.21.11forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.21.10)
1.3.0-snapshot.2+1.21.10-forge15 июл. 2026 г.8Minecraft: 1.21.10forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.21.1)
1.3.0-snapshot.2+1.21.1-forge15 июл. 2026 г.36Minecraft: 1.21.1forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.20)
1.3.0-snapshot.2+1.20-forge15 июл. 2026 г.7Minecraft: 1.20forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.20.6)
1.3.0-snapshot.2+1.20.6-forge15 июл. 2026 г.3Minecraft: 1.20.6forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.20.5)
1.3.0-snapshot.2+1.20.5-forge15 июл. 2026 г.1Minecraft: 1.20.5forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.20.4)
1.3.0-snapshot.2+1.20.4-forge15 июл. 2026 г.3Minecraft: 1.20.4forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.20.3)
1.3.0-snapshot.2+1.20.3-forge15 июл. 2026 г.1Minecraft: 1.20.3forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.20.2)
1.3.0-snapshot.2+1.20.2-forge15 июл. 2026 г.3Minecraft: 1.20.2forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Forge 1.20.1)
1.3.0-snapshot.2+1.20.1-forge15 июл. 2026 г.224Minecraft: 1.20.1forge
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 26.2)
1.3.0-snapshot.2+26.2-fabric15 июл. 2026 г.528Minecraft: 26.2fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 26.1)
1.3.0-snapshot.2+26.1-fabric15 июл. 2026 г.22Minecraft: 26.1fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 26.1.2)
1.3.0-snapshot.2+26.1.2-fabric15 июл. 2026 г.204Minecraft: 26.1.2fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 26.1.1)
1.3.0-snapshot.2+26.1.1-fabric15 июл. 2026 г.10Minecraft: 26.1.1fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.21)
1.3.0-snapshot.2+1.21-fabric15 июл. 2026 г.6Minecraft: 1.21fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.21.9)
1.3.0-snapshot.2+1.21.9-fabric15 июл. 2026 г.3Minecraft: 1.21.9fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.21.8)
1.3.0-snapshot.2+1.21.8-fabric15 июл. 2026 г.14Minecraft: 1.21.8fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.21.7)
1.3.0-snapshot.2+1.21.7-fabric15 июл. 2026 г.3Minecraft: 1.21.7fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.21.6)
1.3.0-snapshot.2+1.21.6-fabric15 июл. 2026 г.2Minecraft: 1.21.6fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.21.5)
1.3.0-snapshot.2+1.21.5-fabric15 июл. 2026 г.5Minecraft: 1.21.5fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.21.4)
1.3.0-snapshot.2+1.21.4-fabric15 июл. 2026 г.19Minecraft: 1.21.4fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.21.3)
1.3.0-snapshot.2+1.21.3-fabric15 июл. 2026 г.3Minecraft: 1.21.3fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.21.2)
1.3.0-snapshot.2+1.21.2-fabric15 июл. 2026 г.2Minecraft: 1.21.2fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.21.11)
1.3.0-snapshot.2+1.21.11-fabric15 июл. 2026 г.109Minecraft: 1.21.11fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.21.10)
1.3.0-snapshot.2+1.21.10-fabric15 июл. 2026 г.10Minecraft: 1.21.10fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.21.1)
1.3.0-snapshot.2+1.21.1-fabric15 июл. 2026 г.36Minecraft: 1.21.1fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.20)
1.3.0-snapshot.2+1.20-fabric15 июл. 2026 г.3Minecraft: 1.20fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.20.6)
1.3.0-snapshot.2+1.20.6-fabric15 июл. 2026 г.10Minecraft: 1.20.6fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.20.5)
1.3.0-snapshot.2+1.20.5-fabric15 июл. 2026 г.2Minecraft: 1.20.5fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.20.4)
1.3.0-snapshot.2+1.20.4-fabric15 июл. 2026 г.13Minecraft: 1.20.4fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.20.3)
1.3.0-snapshot.2+1.20.3-fabric15 июл. 2026 г.2Minecraft: 1.20.3fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.20.2)
1.3.0-snapshot.2+1.20.2-fabric15 июл. 2026 г.3Minecraft: 1.20.2fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
release
Retromod 1.3.0-snapshot.2 (Fabric 1.20.1)
1.3.0-snapshot.2+1.20.1-fabric15 июл. 2026 г.20Minecraft: 1.20.1fabric
Что нового
**Second snapshot of the 1.3.0 line.** Continues the mixin-translation theme into 26.x worldgen: one more real mixin translation (YUNG's `NoiseChunkMixin`), a broad `Registry` value-getter rename fix, a trailing-parameter re-signature, and the new predictive mixin-discovery tooling. Two worldgen limitations found by a headless-server pass are documented rather than shipped.
### Added
- **Removed-`@Shadow`-field demotion, and YUNG's API `NoiseChunkMixin` translated with it (worldgen).** New `MixinShadowFieldDemotion`: when a mixin `@Shadow @Final`s a vanilla field the host deleted, but the field's value is still reachable as a constructor parameter the mixin's own `@Inject` handler captures, the field is demoted to a plain `@Unique` mixin field and the handler is prepended with `this.<field> = <capturedParam>`. `NoiseChunk.noiseSettings` was deleted in the 1.21.5 worldgen refactor (verified absent on 26.x, present on 1.21.1) while the `NoiseSettings` constructor argument is unchanged, so YUNG's `NoiseChunkMixin` (whose aquifer-override reads `noiseSettings.height()/minY()`) is repaired instead of stripped. Gated to 1.21.5+ hosts; the parameter is located by type-matching the field descriptor, so it survives a mod rebuild. Verified on a NeoForge 26.2 dedicated server: yungsapi loads and 49 force-loaded chunks generate cleanly. Retires the `NoiseChunkMixin` blocklist strip. Tested (`MixinShadowFieldDemotionTest`, incl. a byte-for-byte check against the shipped YungsApi 5.1.6 jar).
- **`Registry` value getter `get(Identifier)` -> `getValue(Identifier)` (26.1 rename).** The `ResourceLocation` -> `Identifier` rename had a companion: the registry VALUE getter `Registry.get(Identifier)` (returns the value `T`) became `getValue(Identifier)`, while `get(Identifier)` now returns `Optional<Holder.Reference>` (a different method). A 1.21.1 mod that calls the old value getter (e.g. `BuiltInRegistries.SOUND_EVENT.get(id)`) linked against the vanished `get(Identifier)Object` and died at construct time with `NoSuchMethodError: DefaultedRegistry.get(Identifier)` (YUNG's Better Strongholds, verified) - and the fuzzy resolver couldn't help because `get(Identifier)` still "resolves" by name+params. A descriptor-scoped method redirect (registered for `Registry`/`DefaultedRegistry`/`MappedRegistry`/`DefaultedMappedRegistry`, keyed on the post-remap `Identifier` value-getter descriptor so the `Optional`-returning `get` is untouched) renames just that overload to `getValue`, on all three loaders. Unblocks YUNG's structure mods (and any 1.21.x mod using the value getter) past construction on 26.x. Tested (`RegistryValueGetterRenameTest`).
- **`@Inject` TRAILING-parameter re-signature (`MixinHandlerResignature`).** The #69 engine inserted a LEADING param (the `ServerLevel` prepend); it now also handles a param appended at the END of a target's capture list, right before the `CallbackInfo` trailer (shifting only the `CallbackInfo` slot). 26.1 appended a `ResourceKey<Level>` (the dimension key) to `ChunkGenerator.tryGenerateStructure`, so a 1.21.1 `@Inject` that captured the old 9 params was missing the trailing arg and died `InvalidInjectionException`. Registered for `tryGenerateStructure` with a `StructureSet$StructureSelectionEntry` first-param guard (YUNG's Better Strongholds `DisableVanillaStrongholdsMixin`, verified applied on 26.2). Tested (`MixinHandlerResignatureTest`).
- **Mixin discovery tooling (`mixin-scan` CLI + `mixin-rank`/`mixin-crossjoin`/`mixin-refmap-harvest` scripts).** Enumerate every `@Mixin` injector across a mod corpus, rank targets by corpus frequency (translate the highest-leverage first), and cross a scan against a real MC version diff to predict class-level breaks before a mod crashes. Documented in `scripts/mixin-discovery.md` and CLAUDE.md. Flips broken-mixin discovery from reactive (scroll a crash log) to predictive.
- **Corpus-mined 26.x vanilla method renames (top-40 NeoForge 1.21.1 audit).** A member-level link-check of the 40 most-downloaded NeoForge 1.21.1 mods (transformed to 26.2, scored against the real 26.2 jar) surfaced several common unresolved calls Retromod didn't yet rewrite; each added redirect is owner+descriptor-scoped so a generic name only touches the one overload: `Minecraft.getTimer()` -> `getDeltaTracker()` (11 mods), `Camera.getPosition()` -> `position()` (9), `GameRenderer.getMainCamera()` -> `mainCamera()` (10, a 26.2 rename), and the JOML const-interface widening `VertexConsumer`/`BufferBuilder.addVertex(Matrix4f,FFF)` -> the `Matrix4fc` param (12 + 7; the concrete `Matrix4f` is-a `Matrix4fc`, so only the descriptor changes). Tested (`Corpus26xRenameRedirectTest`).
- **`score <dir> --json` corpus mode.** The `score` command now accepts a directory and dumps each jar's residual missing classes/methods/fields (resolved against a chosen `--mc-jar`) as JSON in one JVM, so a whole mod corpus can be transformed then ranked by highest-leverage break. This is the audit tool the renames above came out of.
- **Corpus-mined 26.x descriptor adaptations (top-50 Fabric+NeoForge 1.21.1 audit).** A deeper link-check (50 Fabric + 40 NeoForge mods transformed to 26.2, scored against the real 26.2 jar) surfaced a tier of breaks where the target method still exists on 26.1 but a primitive TYPE or its STATIC form changed, so a plain rename can't fix them: the descriptor has to be adapted at the call site. Two small, owner+descriptor-scoped mechanisms handle them, targeting real vanilla methods (no reflection, no embedded helper, cross-loader), each verified against the 26.1 AND 26.2 jars (old signature gone, new present) and by a re-audit (each target 24/24/16/16/15/12→0 residual mods, 11 jars improved, 0 regressed, 0 new breaks): `registerConvertingRedirect` (retarget + one primitive conversion on the last arg and/or the return, or a POP) covers `Mth.cos(F)F`/`sin(F)F` widened to `(D)F` (F2D the arg; 16+12 mods), `Window.getGuiScale()D` narrowed to `()I` (I2D the result; 16 mods) and `SoundManager.play(SoundInstance)V` now returning a `SoundEngine$PlayResult` (POP the result; 24 mods); `registerSingletonStaticRedirect` re-expresses a no-arg static helper that became an instance method, covering `Screen.hasControlDown()/hasShiftDown()/hasAltDown()` -> `Minecraft.getInstance().hasX()` (24+24+12 mods); and the existing arg-drop redirect covers `CompoundTag.getList(String,int)` -> `getListOrEmpty(String)` (drop the type-hint int; 15 mods). Structural residuals from the same audit (`ClickEvent`/`HoverEvent` now interfaces, `InputConstants` input-handle redesign, the removed `GlStateManager` blend-factor enums, `Minecraft.screen`) are left for later. Tested (`Corpus26xDescriptorAdaptationTest`).
- **NBT read-API adaptations (1.21.5 refactor; found via a top-60 Fabric 1.20.1 audit, benefits 1.20.1 AND 1.21.1 mods).** A separate 1.20.1 -> 26.2 corpus pass surfaced a broad save/load break: the 1.21.5 NBT refactor changed the common accessors, so a mod's serialization code links against signatures that are gone. Each fix is owner+descriptor-scoped (the co-existing new `Optional`-returning overloads are untouched) and verified against the 26.1 AND 26.2 jars (old gone, new present), by re-audit (each target 12/10/9/9/9 -> 0 residual mods, 5 jars improved, 0 regressed, 0 new breaks) and by `BasicVerifier` (0 new verify errors on the NBT-heavy jars): `CompoundTag.contains(String,int)` -> `contains(String)` (drop the tag-type-hint int), `CompoundTag.getCompound(String)` and `ListTag.getCompound(int)` -> `getCompoundOrEmpty(...)` (the plain getters now return `Optional`), `CompoundTag.remove(String)V` -> `remove(String)Tag` (POP the now-returned removed tag), and `TagParser.parseTag(String)` -> `parseCompoundFully(String)`. Tested (`Corpus26xDescriptorAdaptationTest`). (The same audit confirmed the 1.20.1 residuals are otherwise dominated by the multi-version render-pipeline redesign - `BufferBuilder`/`Tesselator`/`RenderSystem`/`GlStateManager`/`GuiGraphics` drawing - which is structural, and that 1.20.1 intermediary "skew" is NOT mapping-fixable: the dangling ids resolve to removed/renamed APIs, so the version-skew composer yielded no clean additions there, unlike the 1.21.1 `FastColor` case.)
- **`ClickEvent`/`HoverEvent` constructor bridges (1.21.5 text-component rework; 14+8 mods across the audits).** Both became sealed INTERFACES with per-action record subtypes (`ClickEvent.RunCommand`/`OpenUrl`/`ChangePage`/..., `HoverEvent.ShowText`/...), so the old `new ClickEvent(Action,String)` / `new HoverEvent(Action,Object)` constructors are gone and a class-move can't express the typed values. A constructor-to-factory redirect rewrites those `new`s to `com.retromod.polyfill.minecraft.RetroTextEvents`, a reflective (Minecraft-free), fail-safe factory that dispatches on the action enum to the right subtype (open-url -> `OpenUrl(URI)`, run/suggest-command + copy -> the String subtypes, change-page -> `ChangePage(int)`, open-file -> `OpenFile(File)`; an unmappable action or bad value goes inert as `null`). This is the exact dispatch the old constructor did internally, so it is correct by construction. The factory is registered as a synthetic so the Forge/NeoForge per-mod embedder relocates a JPMS-split-package-safe copy (Fabric injects it directly); the redirect appends a `CHECKCAST` since the reflective factory returns `Object`. Verified by re-audit (ctor residuals 14 -> 0 and 8 -> 0, 2 jars improved) and `BasicVerifier` (0 new verify errors on the affected jars). Gated 26.1+ (interfaces on every such host, so the old ctor is genuinely gone). Tested (`Corpus26xDescriptorAdaptationTest`: the ctor-to-factory rewrite + the factory's fail-safe). Runtime click/hover behaviour (that the reflective subtype construction fires) still wants an in-game check.
- **Offline (batch/AOT) path now remaps access wideners and mixin refmaps to the official namespace (found by an in-game 26.2 Fabric launch).** A live launch surfaced two crashes the headless member-linkcheck can't see, because they fire during Fabric's `loadClassTweakers` / mixin bootstrap - BEFORE any member resolution, so there is no crash-report and `latest.log` stops at the mod list. The RUNTIME (`FabricModTransformer`) path already remapped these; the offline `batch`/`transform`/AOT path (`RetromodCli`) did NOT, a parity gap: (1) a mod's access widener stays `accessWidener v1 intermediary`, and 26.1+ runs the `official` namespace, so Fabric's classTweaker reader throws `ClassTweakerFormatException: Namespace (intermediary) does not match current runtime namespace (official)` and the game dies (hit via cloth-config bundled inside AppleSkin); (2) a mixin refmap keeps its intermediary selectors, so an `@Inject` resolves to `net/minecraft/class_XXXX` and Mixin rejects it (`InvalidInjectionException`). Both are now handled in the offline path (top-level AND nested jar-in-jar), remapping the header/namespace and every `class_/method_/field_` token to Mojang. The refmap remapper also gained the combined `data."named:intermediary"` -> `"named:official"` form current Fabric loom emits (AppleSkin's shape), which the previous plain-`data.intermediary` logic missed on BOTH paths - so this also fixes the runtime path for that refmap format. Extracted to shared `AccessWidenerRemapper` / `MixinRefmapRemapper` (one source of truth for runtime + offline). Tested (`AccessWidenerRemapperTest`, `MixinRefmapRemapperTest`); verified in-game (the classTweaker + mixin crashes clear, the mod's mixins apply). (The same launch confirmed snapshot.2 itself reaches the title screen on 26.2 Fabric with translated mods, and noted two follow-ups: the single-jar `transform` command doesn't apply the full intermediary bytecode remap that `batch`/runtime do, and AppleSkin needs a Fabric-API `PayloadTypeRegistry` networking shim - separate from these transform fixes.)
- **26.x GUI 2D-transform migration, Phase 0 (`GuiGraphics.pose().pushPose()` peephole).** 26.x moved GUI rendering off the 3D `PoseStack` onto a 2D `org.joml.Matrix3x2fStack` (`GuiGraphics.pose()` now returns the 2D stack, whose ops are `pushMatrix()/popMatrix()`, not `pushPose()/popPose()`), but `PoseStack` still exists for 3D world rendering, so a type-blind redirect corrupts 3D (design RFC: `docs/design/gui-2d-transform-migration.md`). This first phase rewrites the IMMEDIATE chain a 1.21.1 mod compiles for `guiGraphics.pose().pushPose()` / `.popPose()` (the `pose()` call adjacent to the void op, its result consumed on the spot) to `pose():Matrix3x2fStack` + `pushMatrix()/popMatrix()` + POP: a self-contained two-instruction peephole with no dataflow and no local retyping. Strictly conservative: a stored/non-adjacent stack, the arg-carrying ops (`translate`/`scale`/`mulPose`), and any frame-recompute failure are left untouched (unresolved exactly as before, never worse). NeoForge/Forge, gated 26.1+. Verified on Jade (16 of 20 immediate push/popPose migrated; 0 new bytecode-verify errors across the jar via ASM CheckClassAdapter; the migrated class verifies clean). Tested (`Gui2DTransformMigrationTest`). Visual pixel correctness needs in-game verification; the stored-stack and arg ops are later phases per the RFC.
### Fixed
- **`transform` CLI command now loads the removed-API polyfills (parity with analyze/batch/runtime).** The standalone `transform` command registered only the version-shim chain (or the all-shims fallback) and skipped `PolyfillRegistry.loadAndRegister`, so a transform-only pass left references to removed-vanilla classes dangling: e.g. `net/minecraft/util/LazyLoadedValue` (removed in 26.1, referenced by Jade) stayed named in the output where the runtime/analyze/batch paths redirect it onto the embedded polyfill. `transformCommand` now runs the polyfill pass in BOTH branches, so a transform-only output no longer understates compatibility. Tested (`TransformPolyfillRegressionTest`).
- **`batch` and AOT now apply the removed-API polyfills too.** The shared `registerAuxiliaryRedirects` step (the ONLY way the `batch` JIT path and the AOT compiler reach the transformer) registered class-moves, API shims and member mappings but NOT the polyfills, so a batch-transformed mod referencing a removed-vanilla class (`net/minecraft/util/Tuple`, `LazyLoadedValue`, ...) was left dangling, even though `batch`/AOT is the recommended offline NeoForge flow. It now loads polyfills in that shared step (corpus-verified: raw `net/minecraft/util/Tuple` refs across the 40-mod corpus went 40 -> 0). Tested (`TransformPolyfillRegressionTest`).
- **Fabric intermediary-map version skew (`Minecraft.getTimer`/`getDeltaTracker`).** A top-40 Fabric 1.21.1 corpus audit found the bundled `intermediary-to-mojang.tsv` (harvested with 26.x intermediary ids) misses the 1.21.1-era id for any method Mojang RENAMED since 1.21.1: `getTimer()` became `getDeltaTracker()`, so a distributed 1.21.1 mod calling `method_60646` was left dangling (10 of the top-40 Fabric mods, verified 10 -> 0). Added `method_60646 -> getDeltaTracker` (the 26.x id `method_61966` already mapped). Tested (`IntermediaryMethodSkewTest`). (The audit also surfaced two systematic gaps documented for future work: broader renamed-method version skew, and nested intermediary classes getting outer-only remapping, e.g. `GlStateManager$class_4535`.)
- **Intermediary version skew, systematized (`FastColor$ARGB32`/`ABGR32` -> `ARGB` color helpers).** Following up the `getTimer` skew fix, a reusable composer (`scripts/harvest-intermediary-skew.py`) resolves dangling intermediary ids for a given MC version by composing that version's intermediary tiny (obf<->intermediary) with Mojang's ProGuard mappings (mojang->obf). Running it over the top-50 Fabric corpus's dangling ids found the skew is otherwise MINIMAL for 1.21.1 (the bundled tsv is comprehensive; the remaining danglers are genuine removals - `Explosion` particle getters, `ItemInteractionResult`, the `ARMOR_MATERIAL` registry, model-loading internals - not mappable renames), and pinned ONE real re-minting cluster: the 26.1 `FastColor$ARGB32`/`ABGR32` -> `ARGB` class rename re-minted the color-helper method ids. The enclosing class already resolved (tsv class entry + the `FastColor -> ARGB` class-move), so only the 1.21.1-era method ids were missing; added the 6 dangling ones (`method_57173`/`57174`/`58144`/`59553`/`59554`/`60676` -> `color`/`opaque`/`color`/`as8BitChannel`/`colorFromFloat`/`average`), each verified present on 26.2 `ARGB`, each id absent from the 26.x map, and 0 id conflicts (intermediary ids are append-only, so an id absent from the current map can be given its older mapping with no risk of clobbering another member). Re-audit: the 5 corpus-hit ids went to 0 residual, 0 regressions. Tested (`IntermediaryMethodSkewTest`).
- **Security + quality hardening (multi-agent adversarial review of this snapshot's changes and the untrusted-input surface).** Confirmed and fixed: (1) a save-data `@Inject` handler shared across BOTH a write (`addAdditionalSaveData`) and a read (`readAdditionalSaveData`) target is now DECLINED by the ValueIO adapter (one `CompoundTag` param can't become both `ValueOutput` and `ValueInput`) and soft-fail-stripped instead of adapted to one type and left throwing `InvalidInjectionException` on the other; (2) `collectUnrepairable` now scopes its stale-`CompoundTag` check to captures BEFORE the `CallbackInfo` trailer, so a modern handler with a `CompoundTag` `@Local` is no longer wrongly stripped; (3) the ValueIO strip fallback now keys on name+descriptor, so an unrelated overloaded `@Inject` sharing a stripped handler's name survives; (4) `SyntheticEmbedder.embedIntoJar` now bounds its reads with `ZipSecurity` (per-entry + aggregate cap) instead of raw `readAllBytes` (decompression-bomb DoS on the offline embed path); (5) the AOT cache no longer re-stamps a directory whose wipe left stale entries behind (cache-poisoning: it would have served a previous build's transforms as current); (6) the offline `transform` entry-copy loop gained the aggregate decompressed-size cap the runtime extract paths already had; and (7) `clearRedirectsForTesting` now clears the new converting/singleton redirect maps (test isolation). Tested (new cases in `MixinValueIoAdapterTest`; the rest exercised by the existing suite).
### Research (documented, not yet shipped)
- **Yung's API `BeardifierMixin` confirmed genuinely un-translatable (headless-proven, stays stripped).** Its companion `NoiseChunkMixin` shipped as a real translation (see Added), but `BeardifierMixin` did NOT: research showed it is byte-translatable (its `@Inject` targets `forStructuresInChunk`/`compute` survive on 26.2) and it *applies* cleanly, yet the headless worldgen pass proved it is not launch-safe. With `BeardifierMixin` active, chunk generation throws `IllegalStateException: Parent chunk missing` (`ChunkMap.applyStep`) during initial spawn generation and the dedicated server never reaches `Done` - its enhanced cross-chunk terrain adaptation is incompatible with 26.2's stricter chunk-dependency scheduler, a runtime-behaviour break no bytecode remap fixes. A/B verified on a real NeoForge 26.2 server: both-mixins-translated crashes; NoiseChunk-only (shipped) and both-stripped both reach `Done` and generate 49 force-loaded chunks. So `BeardifierMixin` + the dead `BeardifierAccessor` stay whole-class stripped; the terrain-adaptation feature stays inert and the mod loads. The two mixins are separable in practice (NoiseChunk's attach-to-beardifier handler is an `instanceof` no-op while Beardifier is stripped). This is the same "genuinely un-translatable" class as True Darkness #68, but found at launch rather than statically - and exactly why the hold required a worldgen pass before retiring the strip.
- **YUNG's NeoForge structure mods (Better Strongholds): `StructureProcessorType` registration is structurally incompatible on 26.2, not mechanically bridgeable.** With the two fixes above, Better Strongholds (NeoForge 1.21.1) now constructs and its mixins apply on a 26.2 dedicated server, but structure registration can't be mechanically translated: 26.2 DELETED the `StructureProcessorType`-as-SAM model the mod is built on. Investigated (attempted a `DeferredRegister` bridge) against the NeoForge 26.2-beta server jars and found three stacked blockers: (1) `StructureProcessorType` lost its `codec()` abstract method (now a non-functional marker interface; `Registries.STRUCTURE_PROCESSOR` holds `MapCodec` directly), so the mod's `StructureProcessorType` SAM lambdas reach the `MapCodec` registry unconverted (`ClassCastException: …$$Lambda cannot be cast to MapCodec` -> unbound `processor_list` -> `FatalStartupException`); (2) `StructureProcessor.getType():StructureProcessorType` was renamed to `codec():MapCodec`, so the mod's processor subclasses (which override `getType()` and return their `StructureProcessorType` field) don't implement 26.2's abstract `codec()` and would be un-instantiable at decode, with no mechanical way to reconstruct the `StructureProcessorType`->`MapCodec` relationship the deleted SAM expressed; and (3) the mod BUILDS those SAMs via `invokedynamic` against the now-methodless `StructureProcessorType`. Converting only the `DeferredRegister` registration (blocker 1) leaves 2 and 3, so no bridge makes the mod actually generate. This is the same "structurally redesigned API" class as True Darkness #68 - a re-authoring job, not a redirect. Documented in [Mods That Can't Be Translated](docs/incompatible-mods.md).
beta
Retromod 1.3.0-snapshot.1 (NeoForge 26.2)
1.3.0-snapshot.1+26.2-neoforge13 июл. 2026 г.24Minecraft: 26.2neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 26.1.2)
1.3.0-snapshot.1+26.1.2-neoforge13 июл. 2026 г.15Minecraft: 26.1.2neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.21)
1.3.0-snapshot.1+1.21-neoforge13 июл. 2026 г.2Minecraft: 1.21neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.21.9)
1.3.0-snapshot.1+1.21.9-neoforge13 июл. 2026 г.3Minecraft: 1.21.9neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.21.8)
1.3.0-snapshot.1+1.21.8-neoforge13 июл. 2026 г.2Minecraft: 1.21.8neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.21.7)
1.3.0-snapshot.1+1.21.7-neoforge13 июл. 2026 г.2Minecraft: 1.21.7neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.21.6)
1.3.0-snapshot.1+1.21.6-neoforge13 июл. 2026 г.2Minecraft: 1.21.6neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.21.5)
1.3.0-snapshot.1+1.21.5-neoforge13 июл. 2026 г.2Minecraft: 1.21.5neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.21.4)
1.3.0-snapshot.1+1.21.4-neoforge13 июл. 2026 г.1Minecraft: 1.21.4neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.21.3)
1.3.0-snapshot.1+1.21.3-neoforge13 июл. 2026 г.1Minecraft: 1.21.3neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.21.2)
1.3.0-snapshot.1+1.21.2-neoforge13 июл. 2026 г.1Minecraft: 1.21.2neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.21.11)
1.3.0-snapshot.1+1.21.11-nf13 июл. 2026 г.3Minecraft: 1.21.11neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.21.10)
1.3.0-snapshot.1+1.21.10-nf13 июл. 2026 г.2Minecraft: 1.21.10neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.21.1)
1.3.0-snapshot.1+1.21.1-neoforge13 июл. 2026 г.51Minecraft: 1.21.1neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.20.6)
1.3.0-snapshot.1+1.20.6-neoforge13 июл. 2026 г.2Minecraft: 1.20.6neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.20.5)
1.3.0-snapshot.1+1.20.5-neoforge13 июл. 2026 г.2Minecraft: 1.20.5neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.20.4)
1.3.0-snapshot.1+1.20.4-neoforge13 июл. 2026 г.2Minecraft: 1.20.4neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.20.3)
1.3.0-snapshot.1+1.20.3-neoforge13 июл. 2026 г.2Minecraft: 1.20.3neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.20.2)
1.3.0-snapshot.1+1.20.2-neoforge13 июл. 2026 г.1Minecraft: 1.20.2neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (NeoForge 1.20.1)
1.3.0-snapshot.1+1.20.1-neoforge13 июл. 2026 г.1Minecraft: 1.20.1neoforge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 26.2)
1.3.0-snapshot.1+26.2-forge13 июл. 2026 г.8Minecraft: 26.2forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 26.1)
1.3.0-snapshot.1+26.1-forge13 июл. 2026 г.3Minecraft: 26.1forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 26.1.2)
1.3.0-snapshot.1+26.1.2-forge13 июл. 2026 г.5Minecraft: 26.1.2forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 26.1.1)
1.3.0-snapshot.1+26.1.1-forge13 июл. 2026 г.2Minecraft: 26.1.1forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.21)
1.3.0-snapshot.1+1.21-forge13 июл. 2026 г.2Minecraft: 1.21forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.21.9)
1.3.0-snapshot.1+1.21.9-forge13 июл. 2026 г.3Minecraft: 1.21.9forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.21.8)
1.3.0-snapshot.1+1.21.8-forge13 июл. 2026 г.2Minecraft: 1.21.8forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.21.7)
1.3.0-snapshot.1+1.21.7-forge13 июл. 2026 г.2Minecraft: 1.21.7forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.21.6)
1.3.0-snapshot.1+1.21.6-forge13 июл. 2026 г.2Minecraft: 1.21.6forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.21.5)
1.3.0-snapshot.1+1.21.5-forge13 июл. 2026 г.3Minecraft: 1.21.5forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.21.4)
1.3.0-snapshot.1+1.21.4-forge13 июл. 2026 г.1Minecraft: 1.21.4forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.21.3)
1.3.0-snapshot.1+1.21.3-forge13 июл. 2026 г.2Minecraft: 1.21.3forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.21.2)
1.3.0-snapshot.1+1.21.2-forge13 июл. 2026 г.2Minecraft: 1.21.2forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.21.11)
1.3.0-snapshot.1+1.21.11-forge13 июл. 2026 г.2Minecraft: 1.21.11forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.21.10)
1.3.0-snapshot.1+1.21.10-forge13 июл. 2026 г.1Minecraft: 1.21.10forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.21.1)
1.3.0-snapshot.1+1.21.1-forge13 июл. 2026 г.6Minecraft: 1.21.1forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.20)
1.3.0-snapshot.1+1.20-forge13 июл. 2026 г.2Minecraft: 1.20forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.20.6)
1.3.0-snapshot.1+1.20.6-forge13 июл. 2026 г.3Minecraft: 1.20.6forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.20.5)
1.3.0-snapshot.1+1.20.5-forge13 июл. 2026 г.2Minecraft: 1.20.5forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.20.4)
1.3.0-snapshot.1+1.20.4-forge13 июл. 2026 г.3Minecraft: 1.20.4forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.20.3)
1.3.0-snapshot.1+1.20.3-forge13 июл. 2026 г.4Minecraft: 1.20.3forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.20.2)
1.3.0-snapshot.1+1.20.2-forge13 июл. 2026 г.3Minecraft: 1.20.2forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Forge 1.20.1)
1.3.0-snapshot.1+1.20.1-forge13 июл. 2026 г.27Minecraft: 1.20.1forge
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 26.2)
1.3.0-snapshot.1+26.2-fabric13 июл. 2026 г.182Minecraft: 26.2fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 26.1)
1.3.0-snapshot.1+26.1-fabric13 июл. 2026 г.3Minecraft: 26.1fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 26.1.2)
1.3.0-snapshot.1+26.1.2-fabric13 июл. 2026 г.46Minecraft: 26.1.2fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 26.1.1)
1.3.0-snapshot.1+26.1.1-fabric13 июл. 2026 г.3Minecraft: 26.1.1fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.21)
1.3.0-snapshot.1+1.21-fabric13 июл. 2026 г.3Minecraft: 1.21fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.21.9)
1.3.0-snapshot.1+1.21.9-fabric13 июл. 2026 г.1Minecraft: 1.21.9fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.21.8)
1.3.0-snapshot.1+1.21.8-fabric13 июл. 2026 г.3Minecraft: 1.21.8fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.21.7)
1.3.0-snapshot.1+1.21.7-fabric13 июл. 2026 г.3Minecraft: 1.21.7fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.21.6)
1.3.0-snapshot.1+1.21.6-fabric13 июл. 2026 г.3Minecraft: 1.21.6fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.21.5)
1.3.0-snapshot.1+1.21.5-fabric13 июл. 2026 г.1Minecraft: 1.21.5fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.21.4)
1.3.0-snapshot.1+1.21.4-fabric13 июл. 2026 г.2Minecraft: 1.21.4fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.21.3)
1.3.0-snapshot.1+1.21.3-fabric13 июл. 2026 г.1Minecraft: 1.21.3fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.21.2)
1.3.0-snapshot.1+1.21.2-fabric13 июл. 2026 г.1Minecraft: 1.21.2fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.21.11)
1.3.0-snapshot.1+1.21.11-fabric13 июл. 2026 г.19Minecraft: 1.21.11fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.21.10)
1.3.0-snapshot.1+1.21.10-fabric13 июл. 2026 г.3Minecraft: 1.21.10fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.21.1)
1.3.0-snapshot.1+1.21.1-fabric13 июл. 2026 г.11Minecraft: 1.21.1fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.20)
1.3.0-snapshot.1+1.20-fabric13 июл. 2026 г.2Minecraft: 1.20fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.20.6)
1.3.0-snapshot.1+1.20.6-fabric13 июл. 2026 г.3Minecraft: 1.20.6fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.20.5)
1.3.0-snapshot.1+1.20.5-fabric13 июл. 2026 г.3Minecraft: 1.20.5fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.20.4)
1.3.0-snapshot.1+1.20.4-fabric13 июл. 2026 г.4Minecraft: 1.20.4fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.20.3)
1.3.0-snapshot.1+1.20.3-fabric13 июл. 2026 г.4Minecraft: 1.20.3fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.20.2)
1.3.0-snapshot.1+1.20.2-fabric13 июл. 2026 г.4Minecraft: 1.20.2fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
beta
Retromod 1.3.0-snapshot.1 (Fabric 1.20.1)
1.3.0-snapshot.1+1.20.1-fabric13 июл. 2026 г.4Minecraft: 1.20.1fabric
Что нового
**First snapshot of the 1.3.0 line ("mixin translation").** The theme: stop *soft-failing* mixins and actually translate them. Where 1.2.0 could only STRIP a mixin handler whose target drifted (the feature went inert), 1.3.0 rewrites the handler so the feature keeps working. This snapshot lands the first batch of real translations, retiring four blocklist soft-fails, plus two regression fixes to the re-signature engine caught in review.
### Added
- **ValueIO save-data adapter (#48).** A 1.21.1 mod's `@Inject` into `Entity.addAdditionalSaveData`/`readAdditionalSaveData` (or the BlockEntity `saveAdditional`/`loadAdditional`) captures a `CompoundTag`; the 1.21.5 ValueIO refactor made those methods pass `ValueOutput`/`ValueInput`, so Mixin rejected the handler (`InvalidInjectionException`) and NeoForge cascaded into "broken mod state". `MixinValueIoAdapter` keeps the mod's handler body unchanged (still operating on a real `CompoundTag`) and wraps it: the synthesized handler converts the ValueIO param to a `CompoundTag` through a reflective, Minecraft-free runtime bridge (`ValueIoBridge`, per-mod-embedded on NeoForge/Forge for JPMS split-package safety). Repair-or-strip: if frame recomputation fails, the handler is stripped (the old soft-fail), so it can never ship a `VerifyError`. Retires the Darker Depths `PlayerMixin` blocklist entry.
- **`@Inject` / `@Redirect` / `@WrapOperation` / `@Overwrite` signature-drift re-signaturing (#69).** The 1.21.5 "world/level threading" refactor prepended a `ServerLevel` to many `LivingEntity`/`Entity`/`Mob`/`Player` methods (`doHurtTarget(Entity)` becomes `doHurtTarget(ServerLevel, Entity)`, and so on). `MixinHandlerResignature` inserts the new leading param into the handler descriptor, shifts every local slot, and rewrites the drifted `@At(INVOKE)` injection-point targets, so the handler matches the modern method again. Gated to 1.21.5+ hosts (a no-op where the signatures are intact), re-emitted under `COMPUTE_FRAMES` with a fall-back to the prior soft-fail. Retires Revamped Phantoms' `SweepAttackMixin` `doHurtTarget` entry.
- **MixinExtras injector dispatch.** `@ModifyReturnValue`/`@ModifyExpressionValue`/`@WrapOperation`/... selectors are now routed through the same intermediary-to-Mojang / drift remap as core injectors, with the `require=0` soft-fail net added only when a selector was actually rewritten (so a working MixinExtras mixin stays byte-identical).
- **Revamped Phantoms `PhantomMixin` (#50): a real translation via a deleted-superclass owner-alias.** `FlyingMob` was deleted in ~1.21.2 and Phantom now extends `Mob` directly, but Phantom STILL declares `getDefaultDimensions(Pose)` (its body calls `Mob.getDefaultDimensions`, the former `super`), so the mod's `@ModifyExpressionValue`'s only break was its `@At(INVOKE)` target still naming `FlyingMob`. A `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` method redirect (in the three 1.21.11 to 26.1 shims, intermediary key on Fabric) re-owns the injection point, so the phantom-size feature works instead of being stripped. Retires the `PhantomMixin` blocklist entry. *Known limitation:* on the rarely-used intermediate host range 1.21.2 to 1.21.10, where `FlyingMob` is already gone but that redirect (and the intermediary to Mojang remap) does not apply, the feature hard-fails rather than soft-failing; the maintained 26.1+ path is fully covered, and pre-26.1 hosts are the #55 bridge's scope.
- **Mixin selector remapping is now applied on ALL loaders and the offline CLI, not just the Fabric runtime.** Previously the `@Mixin`/`@At`/`method=` selector remap (method + class redirects) ran only inside the Fabric-runtime path; the NeoForge/Forge and CLI-batch paths did the blocklist strip + re-signature but skipped the selector remap, so an API-rename redirect (the #50 `FlyingMob` alias, the #28 `Painting` class-move on the NeoForge Deeper and Darker, the GameNarrator/setScreen scan-renames) never reached a NeoForge/Forge or offline-transformed mixin's annotation strings. Those paths now run the same full pipeline. (Without this, #50 and the class-moves would have silently regressed to hard injection failures on their actual loaders.)
- **Scan-rename redirects** (from a 19-jar corpus scan of mixin selectors against the 26.x jars): `GameNarrator.sayNow` to `saySystemNow`, `FriendlyByteBuf.readJsonWithCodec` to `readLenientJsonWithCodec` (26.1 epoch), `Minecraft.setScreen` to `setScreenAndShow` (26.2 epoch).
- **1.21.11 to 26.1 vanilla class moves:** `entity/decoration/Painting` + `PaintingVariant` into `entity/decoration/painting/`, `entity/monster/Husk` into `entity/monster/zombie/`, and the `MobSpawnType` to `EntitySpawnReason` rename (all verified against the 26.1 and 26.2 jars).
### Fixed
- **#28 (Deeper and Darker): a stale blocklist entry that protected nothing.** The `#28` entry named a `PaintingItemMixin`/`deeperdarker$decrementStackOnServer` handler that does NOT exist in the current jar (a silent no-op that hid the mod's real drift). Replaced with entries for the REAL classes: `HangingEntityItemMixin.appendHoverText` (its target was re-signatured `List<Component>` into `TooltipDisplay` + `Consumer`, a param split, and its body reads the deleted `Painting.VARIANT_MAP_CODEC`/`CustomData.read`, so it is genuinely unrepairable and is stripped, the variant tooltip going inert) and `PaintingMixin.dropItem` (a `ServerLevel` signature drift on a generic name, stripped). The `Painting` class-move fixes the `@Mixin` target so `PaintingMixin.getPickResult` now applies natively.
- **Re-signature engine: a `@Local` captured after the CallbackInfo trailer could turn a soft-fail into a hard crash.** The parameter-annotation decline guard was bounded at the CallbackInfo index, but MixinExtras `@Local`/`@Share` locals sit AFTER it, and the re-signature does not shift the parameter-annotation arrays, so an inserted leading param misaligned the `@Local` onto the wrong parameter, an `InvalidInjectionException` (which `COMPUTE_FRAMES` can't catch) on exactly the #69 `doHurtTarget`-family methods. The guard is now full-width, so any parameter-annotated handler declines and keeps its soft-fail.
- **Re-signature engine: the `@Inject` `method=` selector was rewritten decoupled from the handler re-signature.** `rewriteAnnotationDrift` eagerly rewrote an `@Inject`'s top-level `method=` selector to new-form even when `insertParams` later declined, shipping a new-form selector against an un-re-signatured handler (a hard descriptor mismatch). The top-level `@Inject method=` rewrite is now owned solely by the coupled `insertParams` path; the `@At target=` injection-point rewrites (independent of the handler signature) are unchanged.
- **Owner-only mixin method redirects were silently dropped.** The mixin target-redirect builder only recorded a redirect when the method NAME changed, so an owner-only redirect (the #50 `FlyingMob.getDefaultDimensions` to `Mob.getDefaultDimensions` alias) never reached the `@At`/`method=` remap. It now records the owner-qualified form for ANY change (owner, name, or descriptor); the owner-agnostic bare-name form still requires a genuine name change on a globally-unique obfuscated name.
### Research (documented, not yet shipped)
- **True Darkness (#68) confirmed genuinely un-translatable.** `LightTexture` was deleted and redesigned into `Lightmap` (a GPU texture / UBO produced by a separate `LightmapRenderStateExtractor`), so the mixin's `@Shadow` of the old CPU `DynamicTexture` field has no equivalent and the flicker/dirty state moved to a different class. A real fix is porting the mod to the 26.x render pipeline, outside a mechanical transformer. The whole-class strip stays.
- **Yung's API `BeardifierMixin`/`NoiseChunkMixin` confirmed repairable, held pending verification.** Research (javap on 26.1/26.2) confirmed both are mechanically repairable (Beardifier: un-strip the mixin, strip only the dead `BeardifierAccessor`; NoiseChunk: demote the removed `@Shadow @Final noiseSettings` to a `@Unique` field captured from the surviving constructor param). They are HELD in this snapshot because re-enabling interdependent worldgen mixins requires a headless-server worldgen pass first (a mis-repair cascades to all chunk generation). The blocklist reasons carry the full mechanism.
release
Retromod 1.2.0 (NeoForge 26.2)
1.2.0+26.2-neoforge10 июл. 2026 г.43Minecraft: 26.2neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 26.1.2)
1.2.0+26.1.2-neoforge10 июл. 2026 г.20Minecraft: 26.1.2neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.21)
1.2.0+1.21-neoforge10 июл. 2026 г.2Minecraft: 1.21neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.21.9)
1.2.0+1.21.9-neoforge10 июл. 2026 г.4Minecraft: 1.21.9neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.21.8)
1.2.0+1.21.8-neoforge10 июл. 2026 г.3Minecraft: 1.21.8neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.21.7)
1.2.0+1.21.7-neoforge10 июл. 2026 г.2Minecraft: 1.21.7neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.21.6)
1.2.0+1.21.6-neoforge10 июл. 2026 г.3Minecraft: 1.21.6neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.21.5)
1.2.0+1.21.5-neoforge10 июл. 2026 г.2Minecraft: 1.21.5neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.21.4)
1.2.0+1.21.4-neoforge10 июл. 2026 г.7Minecraft: 1.21.4neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.21.3)
1.2.0+1.21.3-neoforge10 июл. 2026 г.2Minecraft: 1.21.3neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.21.2)
1.2.0+1.21.2-neoforge10 июл. 2026 г.2Minecraft: 1.21.2neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.21.11)
1.2.0+1.21.11-neoforge10 июл. 2026 г.9Minecraft: 1.21.11neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.21.10)
1.2.0+1.21.10-neoforge10 июл. 2026 г.1Minecraft: 1.21.10neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.21.1)
1.2.0+1.21.1-neoforge10 июл. 2026 г.143Minecraft: 1.21.1neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.20.6)
1.2.0+1.20.6-neoforge10 июл. 2026 г.2Minecraft: 1.20.6neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.20.5)
1.2.0+1.20.5-neoforge10 июл. 2026 г.2Minecraft: 1.20.5neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.20.4)
1.2.0+1.20.4-neoforge10 июл. 2026 г.1Minecraft: 1.20.4neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.20.3)
1.2.0+1.20.3-neoforge10 июл. 2026 г.3Minecraft: 1.20.3neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.20.2)
1.2.0+1.20.2-neoforge10 июл. 2026 г.3Minecraft: 1.20.2neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (NeoForge 1.20.1)
1.2.0+1.20.1-neoforge10 июл. 2026 г.17Minecraft: 1.20.1neoforge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 26.2)
1.2.0+26.2-forge10 июл. 2026 г.22Minecraft: 26.2forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 26.1)
1.2.0+26.1-forge10 июл. 2026 г.4Minecraft: 26.1forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 26.1.2)
1.2.0+26.1.2-forge10 июл. 2026 г.8Minecraft: 26.1.2forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 26.1.1)
1.2.0+26.1.1-forge10 июл. 2026 г.2Minecraft: 26.1.1forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.21)
1.2.0+1.21-forge10 июл. 2026 г.3Minecraft: 1.21forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.21.9)
1.2.0+1.21.9-forge10 июл. 2026 г.2Minecraft: 1.21.9forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.21.8)
1.2.0+1.21.8-forge10 июл. 2026 г.2Minecraft: 1.21.8forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.21.7)
1.2.0+1.21.7-forge10 июл. 2026 г.2Minecraft: 1.21.7forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.21.6)
1.2.0+1.21.6-forge10 июл. 2026 г.2Minecraft: 1.21.6forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.21.5)
1.2.0+1.21.5-forge10 июл. 2026 г.2Minecraft: 1.21.5forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.21.4)
1.2.0+1.21.4-forge10 июл. 2026 г.8Minecraft: 1.21.4forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.21.3)
1.2.0+1.21.3-forge10 июл. 2026 г.2Minecraft: 1.21.3forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.21.2)
1.2.0+1.21.2-forge10 июл. 2026 г.3Minecraft: 1.21.2forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.21.11)
1.2.0+1.21.11-forge10 июл. 2026 г.13Minecraft: 1.21.11forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.21.10)
1.2.0+1.21.10-forge10 июл. 2026 г.3Minecraft: 1.21.10forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.21.1)
1.2.0+1.21.1-forge10 июл. 2026 г.17Minecraft: 1.21.1forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.20)
1.2.0+1.20-forge10 июл. 2026 г.5Minecraft: 1.20forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.20.6)
1.2.0+1.20.6-forge10 июл. 2026 г.2Minecraft: 1.20.6forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.20.5)
1.2.0+1.20.5-forge10 июл. 2026 г.5Minecraft: 1.20.5forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.20.4)
1.2.0+1.20.4-forge10 июл. 2026 г.3Minecraft: 1.20.4forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.20.3)
1.2.0+1.20.3-forge10 июл. 2026 г.3Minecraft: 1.20.3forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.20.2)
1.2.0+1.20.2-forge10 июл. 2026 г.3Minecraft: 1.20.2forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Forge 1.20.1)
1.2.0+1.20.1-forge10 июл. 2026 г.108Minecraft: 1.20.1forge
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 26.2)
1.2.0+26.2-fabric10 июл. 2026 г.293Minecraft: 26.2fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 26.1)
1.2.0+26.1-fabric10 июл. 2026 г.8Minecraft: 26.1fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 26.1.2)
1.2.0+26.1.2-fabric10 июл. 2026 г.114Minecraft: 26.1.2fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 26.1.1)
1.2.0+26.1.1-fabric10 июл. 2026 г.4Minecraft: 26.1.1fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.21)
1.2.0+1.21-fabric10 июл. 2026 г.5Minecraft: 1.21fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.21.9)
1.2.0+1.21.9-fabric10 июл. 2026 г.6Minecraft: 1.21.9fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.21.8)
1.2.0+1.21.8-fabric10 июл. 2026 г.14Minecraft: 1.21.8fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.21.7)
1.2.0+1.21.7-fabric10 июл. 2026 г.2Minecraft: 1.21.7fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.21.6)
1.2.0+1.21.6-fabric10 июл. 2026 г.4Minecraft: 1.21.6fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.21.5)
1.2.0+1.21.5-fabric10 июл. 2026 г.5Minecraft: 1.21.5fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.21.4)
1.2.0+1.21.4-fabric10 июл. 2026 г.5Minecraft: 1.21.4fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.21.3)
1.2.0+1.21.3-fabric10 июл. 2026 г.2Minecraft: 1.21.3fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.21.2)
1.2.0+1.21.2-fabric10 июл. 2026 г.3Minecraft: 1.21.2fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.21.11)
1.2.0+1.21.11-fabric10 июл. 2026 г.55Minecraft: 1.21.11fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.21.10)
1.2.0+1.21.10-fabric10 июл. 2026 г.4Minecraft: 1.21.10fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.21.1)
1.2.0+1.21.1-fabric10 июл. 2026 г.32Minecraft: 1.21.1fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.20)
1.2.0+1.20-fabric10 июл. 2026 г.3Minecraft: 1.20fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.20.6)
1.2.0+1.20.6-fabric10 июл. 2026 г.3Minecraft: 1.20.6fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.20.5)
1.2.0+1.20.5-fabric10 июл. 2026 г.2Minecraft: 1.20.5fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.20.4)
1.2.0+1.20.4-fabric10 июл. 2026 г.3Minecraft: 1.20.4fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.20.3)
1.2.0+1.20.3-fabric10 июл. 2026 г.3Minecraft: 1.20.3fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.20.2)
1.2.0+1.20.2-fabric10 июл. 2026 г.3Minecraft: 1.20.2fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0 (Fabric 1.20.1)
1.2.0+1.20.1-fabric10 июл. 2026 г.7Minecraft: 1.20.1fabric
Что нового
**Stable release ("the general update").** Promoted from `1.2.0-rc.2` with no code changes since that candidate: a final full build + test pass, self-hash re-embedded. The 1.2.0 line accumulated across `1.2.0-snapshot.1`–`snapshot.9` → `rc.1` → `rc.2` (the sections below have the details); headline work over 1.1.0 includes Forge 26.2 hosts (EventBus 6→7 bridge), 26.x worldgen for structure mods (Yung's family), a 1.12.2 pre-1.13 Forge baseline, Sinytra-parity correctness fixes, and Forge→NeoForge synthetic bridges. The final `rc.2` fixes: `OreBlock` → `DropExperienceBlock` (#145, Isle of Berk), the nested `ExtendedScreenHandlerType$ExtendedFactory` redirect (#147, VillagerViewer), and the docs-site Probe security fix (DOM-XSS + jar-reader hardening) with an accuracy pass (deep-integration mods such as Applied Energistics 2 now correctly read as incompatible; the mixin note counts actual mixin classes).
release
Retromod 1.2.0-rc.1 (NeoForge 26.2)
1.2.0-rc.1+26.2-neoforge6 июл. 2026 г.36Minecraft: 26.2neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 26.1.2)
1.2.0-rc.1+26.1.2-neoforge6 июл. 2026 г.25Minecraft: 26.1.2neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.21)
1.2.0-rc.1+1.21-neoforge6 июл. 2026 г.3Minecraft: 1.21neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.21.9)
1.2.0-rc.1+1.21.9-neoforge6 июл. 2026 г.7Minecraft: 1.21.9neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.21.8)
1.2.0-rc.1+1.21.8-neoforge6 июл. 2026 г.6Minecraft: 1.21.8neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.21.7)
1.2.0-rc.1+1.21.7-neoforge6 июл. 2026 г.0Minecraft: 1.21.7neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.21.6)
1.2.0-rc.1+1.21.6-neoforge6 июл. 2026 г.4Minecraft: 1.21.6neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.21.5)
1.2.0-rc.1+1.21.5-neoforge6 июл. 2026 г.3Minecraft: 1.21.5neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.21.4)
1.2.0-rc.1+1.21.4-neoforge6 июл. 2026 г.2Minecraft: 1.21.4neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.21.3)
1.2.0-rc.1+1.21.3-neoforge6 июл. 2026 г.1Minecraft: 1.21.3neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.21.2)
1.2.0-rc.1+1.21.2-neoforge6 июл. 2026 г.2Minecraft: 1.21.2neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.21.11)
1.2.0-rc.1+1.21.11-neoforge6 июл. 2026 г.11Minecraft: 1.21.11neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.21.10)
1.2.0-rc.1+1.21.10-neoforge6 июл. 2026 г.6Minecraft: 1.21.10neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.21.1)
1.2.0-rc.1+1.21.1-neoforge6 июл. 2026 г.86Minecraft: 1.21.1neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.20.6)
1.2.0-rc.1+1.20.6-neoforge6 июл. 2026 г.3Minecraft: 1.20.6neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.20.5)
1.2.0-rc.1+1.20.5-neoforge6 июл. 2026 г.2Minecraft: 1.20.5neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.20.4)
1.2.0-rc.1+1.20.4-neoforge6 июл. 2026 г.3Minecraft: 1.20.4neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.20.3)
1.2.0-rc.1+1.20.3-neoforge6 июл. 2026 г.4Minecraft: 1.20.3neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.20.2)
1.2.0-rc.1+1.20.2-neoforge6 июл. 2026 г.6Minecraft: 1.20.2neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (NeoForge 1.20.1)
1.2.0-rc.1+1.20.1-neoforge6 июл. 2026 г.9Minecraft: 1.20.1neoforge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 26.2)
1.2.0-rc.1+26.2-forge6 июл. 2026 г.21Minecraft: 26.2forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 26.1)
1.2.0-rc.1+26.1-forge6 июл. 2026 г.5Minecraft: 26.1forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 26.1.2)
1.2.0-rc.1+26.1.2-forge6 июл. 2026 г.7Minecraft: 26.1.2forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 26.1.1)
1.2.0-rc.1+26.1.1-forge6 июл. 2026 г.0Minecraft: 26.1.1forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.21)
1.2.0-rc.1+1.21-forge6 июл. 2026 г.4Minecraft: 1.21forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.21.9)
1.2.0-rc.1+1.21.9-forge6 июл. 2026 г.6Minecraft: 1.21.9forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.21.8)
1.2.0-rc.1+1.21.8-forge6 июл. 2026 г.3Minecraft: 1.21.8forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.21.7)
1.2.0-rc.1+1.21.7-forge6 июл. 2026 г.1Minecraft: 1.21.7forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.21.6)
1.2.0-rc.1+1.21.6-forge6 июл. 2026 г.1Minecraft: 1.21.6forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.21.5)
1.2.0-rc.1+1.21.5-forge6 июл. 2026 г.1Minecraft: 1.21.5forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.21.4)
1.2.0-rc.1+1.21.4-forge6 июл. 2026 г.1Minecraft: 1.21.4forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.21.3)
1.2.0-rc.1+1.21.3-forge6 июл. 2026 г.2Minecraft: 1.21.3forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.21.2)
1.2.0-rc.1+1.21.2-forge6 июл. 2026 г.1Minecraft: 1.21.2forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.21.11)
1.2.0-rc.1+1.21.11-forge6 июл. 2026 г.7Minecraft: 1.21.11forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.21.10)
1.2.0-rc.1+1.21.10-forge6 июл. 2026 г.2Minecraft: 1.21.10forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.21.1)
1.2.0-rc.1+1.21.1-forge6 июл. 2026 г.18Minecraft: 1.21.1forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.20)
1.2.0-rc.1+1.20-forge6 июл. 2026 г.3Minecraft: 1.20forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.20.6)
1.2.0-rc.1+1.20.6-forge6 июл. 2026 г.4Minecraft: 1.20.6forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.20.5)
1.2.0-rc.1+1.20.5-forge6 июл. 2026 г.3Minecraft: 1.20.5forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.20.4)
1.2.0-rc.1+1.20.4-forge6 июл. 2026 г.2Minecraft: 1.20.4forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.20.3)
1.2.0-rc.1+1.20.3-forge6 июл. 2026 г.2Minecraft: 1.20.3forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.20.2)
1.2.0-rc.1+1.20.2-forge6 июл. 2026 г.2Minecraft: 1.20.2forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Forge 1.20.1)
1.2.0-rc.1+1.20.1-forge6 июл. 2026 г.70Minecraft: 1.20.1forge
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 26.2)
1.2.0-rc.1+26.2-fabric6 июл. 2026 г.165Minecraft: 26.2fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 26.1)
1.2.0-rc.1+26.1-fabric6 июл. 2026 г.5Minecraft: 26.1fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 26.1.2)
1.2.0-rc.1+26.1.2-fabric6 июл. 2026 г.72Minecraft: 26.1.2fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 26.1.1)
1.2.0-rc.1+26.1.1-fabric6 июл. 2026 г.4Minecraft: 26.1.1fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.21)
1.2.0-rc.1+1.21-fabric6 июл. 2026 г.4Minecraft: 1.21fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.21.9)
1.2.0-rc.1+1.21.9-fabric6 июл. 2026 г.3Minecraft: 1.21.9fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.21.8)
1.2.0-rc.1+1.21.8-fabric6 июл. 2026 г.4Minecraft: 1.21.8fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.21.7)
1.2.0-rc.1+1.21.7-fabric6 июл. 2026 г.3Minecraft: 1.21.7fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.21.6)
1.2.0-rc.1+1.21.6-fabric6 июл. 2026 г.3Minecraft: 1.21.6fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.21.5)
1.2.0-rc.1+1.21.5-fabric6 июл. 2026 г.4Minecraft: 1.21.5fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.21.4)
1.2.0-rc.1+1.21.4-fabric6 июл. 2026 г.9Minecraft: 1.21.4fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.21.3)
1.2.0-rc.1+1.21.3-fabric6 июл. 2026 г.4Minecraft: 1.21.3fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.21.2)
1.2.0-rc.1+1.21.2-fabric6 июл. 2026 г.6Minecraft: 1.21.2fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.21.11)
1.2.0-rc.1+1.21.11-fabric6 июл. 2026 г.63Minecraft: 1.21.11fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.21.10)
1.2.0-rc.1+1.21.10-fabric6 июл. 2026 г.4Minecraft: 1.21.10fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.21.1)
1.2.0-rc.1+1.21.1-fabric6 июл. 2026 г.37Minecraft: 1.21.1fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.20)
1.2.0-rc.1+1.20-fabric6 июл. 2026 г.5Minecraft: 1.20fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.20.6)
1.2.0-rc.1+1.20.6-fabric6 июл. 2026 г.4Minecraft: 1.20.6fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.20.5)
1.2.0-rc.1+1.20.5-fabric6 июл. 2026 г.1Minecraft: 1.20.5fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.20.4)
1.2.0-rc.1+1.20.4-fabric6 июл. 2026 г.3Minecraft: 1.20.4fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.20.3)
1.2.0-rc.1+1.20.3-fabric6 июл. 2026 г.2Minecraft: 1.20.3fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.20.2)
1.2.0-rc.1+1.20.2-fabric6 июл. 2026 г.3Minecraft: 1.20.2fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-rc.1 (Fabric 1.20.1)
1.2.0-rc.1+1.20.1-fabric6 июл. 2026 г.16Minecraft: 1.20.1fabric
Что нового
**First release candidate for 1.2.0 ("the general update").** Promotes the accumulated snapshot line (snapshot.1 through the unreleased snapshot.9) to a release candidate. Across the 1.2.0 line this delivers MC 26.1.x + 26.2 as the maintained targets on Fabric, NeoForge, and Forge (Forge 65.x, EventBus 6 -> 7), the Forge -> NeoForge migration spine, the 26.2 render-API stand-ins, worldgen on 26.x, and the transform-level 1.12.2 groundwork plus its load layer. The changes new since snapshot.8 are under Fixed below; this rc also closed a cleanup pass:
- **Phantom-target cleanup.** *(done)* Deleted the 10 third-party API shims that were entirely phantom (`AutoRegLibApiShim`, `JadeWailaApiShim`, `PatchouliApiShim`, `BaublesApiShim`, `ThermalApiShim`, `NeiApiShim`, `WailaApiShim`, `FabricShieldLibApiShim`, `OwoLibApiShim`, `LbaApiShim`): every redirect they registered pointed at a never-written embedded class, so they were dead weight (removed their `.java` files and their `META-INF/services/com.retromod.core.VersionShim` entries). That takes the phantom count from 97 to 51. The remaining 51 live inside otherwise-valid core version shims (mixed with real registrations) and are paid down over time. New `PhantomBaselineTest` fences the set against `phantom-baseline.txt` so any NEW phantom fails the build (and fixing one prompts shrinking the baseline). The snapshot.8 runtime guard already made all of these fail-safe; this removes the underlying debt.
- **`method_14452` PlacementModifier mapping fix.** *(done, see Fixed below.)*
- **Multi-mod `mcmod.info` tomls.** *(done, see Fixed below.)*
**Deferred out of 1.2.0 (tracked, roll to a 1.2.x follow-up, not a blocker for this rc):**
- **ClientPlayNetworking v1 raw-bytes bridge (#51)**, **the §B3 armor-layer `EquipmentClientInfo` bridge (#70)**, and **the Sinytra Tier B bundle (#94, covering #9/#11/#12/#13).** These were staged at the transform level during the snapshot cycle but still need in-game launch validation on their target hosts, so they follow the rc rather than gate it.
- **Fully *running* real 1.12.2 mods** (beyond the class-load + registration-event stubs this rc ships) is a per-mod chase through a decade of removed systems and earns its own release: the 1.4.0 line, "The 1.12.2 update", in `ROADMAP.md`.
### Fixed
- **`new ResourceLocation(namespace, path)` no longer corrupts to a VerifyError (#135 Wyrms of Nyrus, #136, #121).** The polyfill that rewrites the removed `ResourceLocation` constructors to the static factories registered them as plain *method* redirects, which only swapped the target and left the uninitialized `NEW`/`DUP` on the stack with an `INVOKESPECIAL` against the factory: `VerifyError: Bad type on operand stack` ("Type uninitialized 0 ... is not assignable") at class load in any mod building a `ResourceLocation` (the canonical 1.12.2 idiom). It now uses `registerConstructorRedirect` (which strips the `NEW`/`DUP` and emits `INVOKESTATIC`), gated to hosts >= 1.20.5 where those constructors were actually removed (on 1.20.1-1.20.4 the ctor still exists, and firing it there was itself the corruption). One fix clears the same VerifyError across three reports. Validated in-game on Forge 1.20.1: Wyrms of Nyrus advanced past the VerifyError (zero VerifyErrors in the crash).
- **1.12.2 removed registration/model events bridged so the mod loads (#134).** A 1.12.2 mod's `@SubscribeEvent` handler taking `RegistryEvent.Register` (the pre-1.13 registration event) or `ModelRegistryEvent` (the pre-1.17 model-registration event) hit `NoClassDefFoundError`. Retromod now embeds `RegistryEvent`/`$Register`/`$MissingMappings` and `ModelRegistryEvent` as synthetic classes that are a valid subtype of the host's eventbus `Event` (a class on EventBus 6 and NeoForge, a marker interface on EventBus 7, resolved by probing the running host so the stub `extends` or `implements` accordingly). The event never fires on a modern host, so the registration-via-event stays inert, but the class loads and event-bus registration completes without throwing. Skipped on the offline CLI where the host eventbus can't be resolved (no worse than before). Validated in-game on Forge 1.20.1: Mob Control Wands advanced past `RegistryEvent$Register`.
- **NeoForge `AddReloadListenerEvent` bridged to 26.2's `AddServerReloadListenersEvent` (#139, Legendary Survival Overhaul).** NeoForge renamed the event around 26.x and made `addListener` require a `ResourceLocation`/`Identifier` id. A 1.21.1 mod referencing the old event hit `NoClassDefFoundError`. Retromod class-redirects the event (fixing the crash and retargeting the `@SubscribeEvent` parameter, both are game-bus events) and bridges the old one-arg `addListener(listener)` call to the new two-arg form via a shim that synthesizes a deterministic id, so the reload listener actually registers.
- **1.12.2 mods no longer die at class-load on removed Forge API types (#131/#132/#134/#135, load layer).** A 1.12.2 Forge mod's class `implements`/references a Forge type that later Forge deleted (`IWorldGenerator` and `IGuiHandler` and `IMessage`/`IMessageHandler` in 1.13, `IForgeRegistryEntry` in 1.17), so the class can't even load (`NoClassDefFoundError`) on a modern Forge/NeoForge host. `Forge_1_12_2_to_1_13_2` now embeds an empty stub interface per removed type and redirects the old name onto it, so those classes load. **Scope:** this fixes the reported class-load errors; the paired registration idioms (`GameRegistry.registerWorldGenerator`, `NetworkRegistry.registerGuiHandler`, `SimpleNetworkWrapper`) target other removed classes and stay inert until bridged, so a mod may surface a next error there. `CreativeModeTab.<init>(String)` (#133, a constructor-to-builder bridge) is a separate follow-up. Part of the ongoing 1.12.2 baseline (#79); the load layer is transform-tested, full in-game running still needs iteration against the real jars.
- **1.12.2 `mcmod.info` with multiple mods now declares every modid (#115).** A single 1.12.2 jar can list several mods in one `mcmod.info` (The Betweenlands ships `thebetweenlands` + `mclib`). The synthesized `mods.toml` previously kept only the first entry, so the extra modids were never declared, FML never registered them, and any class or registration keyed on the second modid failed. `ForgeModTransformer.generateTomlFromMcmodInfo` now JSON-parses the `mcmod.info` (both the array form and the `{"modList":[...]}` v2 wrapper) and emits a `[[mods]]` block plus its own dependency tables per entry, with a lenient regex fallback preserving the old single-mod behavior for an unparseable file. Because the JSON path decodes escapes into real quotes/newlines, the emitted `version` is validated against a `[A-Za-z0-9._-]` allowlist (falling back to `1.0.0`) so a crafted `mcmod.info` can't break out of the quoted TOML value to inject keys or spoof another modid, and the parse degrades to the regex fallback on any `Throwable` (including a `StackOverflowError` from deeply-nested input).
- **Custom `PlacementModifier.getPositions` no longer mis-renamed to `count` (#114).** The Fabric intermediary short-name `method_14452` is ambiguous: verified against Fabric Yarn 1.18.2 through 1.21.4 it names BOTH `PlacementModifier.getPositions(context, random, BlockPos) -> Stream` (Mojang `getPositions`) AND a separate `getCount(random, BlockPos) -> int` (Mojang `count`). The name-only 1.21.4 `intermediary-to-mojang.tsv` kept just `count`, so a mod's `getPositions` override was renamed to `count`: it stopped overriding the abstract method and threw `AbstractMethodError` during chunk generation on 26.x (any mod with a custom placement modifier). `IntermediaryToMojangMapper.applyTo` now registers all four descriptor variants (two eras: 1.18.x `java/util/Random`, 1.19+ `RandomSource`) so the transformer's ambiguous-name fallback resolves each site by descriptor. The systemic follow-up (a descriptor-qualified harvest so the tsv itself carries ambiguity) is noted in code.
- **Removed Fabric v0 `ClientTickCallback` bridged to `ClientTickEvents` (#129 Chat Bubbles).** `net.fabricmc.fabric.api.event.client.ClientTickCallback` (SAM `tick(MinecraftClient)`, static `EVENT`) was deleted from Fabric API around 1.16, so a 1.15-era mod referencing it dies with `NoClassDefFoundError` while its entrypoint class is even being loaded. Retromod now embeds a synthetic `ClientTickCallback` interface (kept under `com/retromod/...` so it can't split-package with the loader module) that preserves the `tick` SAM and the `EVENT` field; `EVENT` is wired in `<clinit>` to an array-backed event whose invoker fires once per client tick via `ClientTickEvents.END_CLIENT_TICK`. Gated to 26.1+ (the SAM uses the Mojang `Minecraft` type). Implemented pending in-game verification.
- **AOT no longer aborts a whole mod on one un-recomputable class (#125 MineColonies, #127 The Flying Things).** The AOT compiler's last-resort per-class path recomputes stack-map frames with `COMPUTE_FRAMES`, which can throw deep inside ASM (`Frame.merge` -> `ArrayIndexOutOfBoundsException` / `NegativeArraySizeException`) on a class whose frames can't be recomputed after our rewrites. That exception propagated out and failed the entire jar's AOT, dropping the mod back to un-transformed bytes. It now falls back per-class to the robust main (JIT) transformer (which has its own ship-original frame-corruption guard), then to the original bytes, and never lets one class take down the jar. This mirrors the snapshot.8 frame-corruption fallback that already protected the JIT path.
release
Retromod 1.2.0-snapshot.8 (NeoForge 26.2)
1.2.0-snapshot.8+26.2-neoforge4 июл. 2026 г.28Minecraft: 26.2neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 26.1.2)
1.2.0-snapshot.8+26.1.2-neoforge4 июл. 2026 г.20Minecraft: 26.1.2neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.21)
1.2.0-snapshot.8+1.21-neoforge4 июл. 2026 г.2Minecraft: 1.21neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.21.9)
1.2.0-snapshot.8+1.21.9-neoforge4 июл. 2026 г.1Minecraft: 1.21.9neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.21.8)
1.2.0-snapshot.8+1.21.8-neoforge4 июл. 2026 г.2Minecraft: 1.21.8neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.21.7)
1.2.0-snapshot.8+1.21.7-neoforge4 июл. 2026 г.1Minecraft: 1.21.7neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.21.6)
1.2.0-snapshot.8+1.21.6-neoforge4 июл. 2026 г.2Minecraft: 1.21.6neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.21.5)
1.2.0-snapshot.8+1.21.5-neoforge4 июл. 2026 г.2Minecraft: 1.21.5neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.21.4)
1.2.0-snapshot.8+1.21.4-neoforge4 июл. 2026 г.4Minecraft: 1.21.4neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.21.3)
1.2.0-snapshot.8+1.21.3-neoforge4 июл. 2026 г.2Minecraft: 1.21.3neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.21.2)
1.2.0-snapshot.8+1.21.2-neoforge4 июл. 2026 г.3Minecraft: 1.21.2neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.21.11)
1.2.0-snapshot.8+1.21.11-nf4 июл. 2026 г.1Minecraft: 1.21.11neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.21.10)
1.2.0-snapshot.8+1.21.10-nf4 июл. 2026 г.4Minecraft: 1.21.10neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.21.1)
1.2.0-snapshot.8+1.21.1-neoforge4 июл. 2026 г.57Minecraft: 1.21.1neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.20.6)
1.2.0-snapshot.8+1.20.6-neoforge4 июл. 2026 г.1Minecraft: 1.20.6neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.20.5)
1.2.0-snapshot.8+1.20.5-neoforge4 июл. 2026 г.3Minecraft: 1.20.5neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.20.4)
1.2.0-snapshot.8+1.20.4-neoforge4 июл. 2026 г.3Minecraft: 1.20.4neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.20.3)
1.2.0-snapshot.8+1.20.3-neoforge4 июл. 2026 г.3Minecraft: 1.20.3neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.20.2)
1.2.0-snapshot.8+1.20.2-neoforge4 июл. 2026 г.4Minecraft: 1.20.2neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (NeoForge 1.20.1)
1.2.0-snapshot.8+1.20.1-neoforge4 июл. 2026 г.6Minecraft: 1.20.1neoforge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 26.2)
1.2.0-snapshot.8+26.2-forge4 июл. 2026 г.8Minecraft: 26.2forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 26.1)
1.2.0-snapshot.8+26.1-forge4 июл. 2026 г.2Minecraft: 26.1forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 26.1.2)
1.2.0-snapshot.8+26.1.2-forge4 июл. 2026 г.3Minecraft: 26.1.2forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 26.1.1)
1.2.0-snapshot.8+26.1.1-forge4 июл. 2026 г.1Minecraft: 26.1.1forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.21)
1.2.0-snapshot.8+1.21-forge4 июл. 2026 г.3Minecraft: 1.21forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.21.9)
1.2.0-snapshot.8+1.21.9-forge4 июл. 2026 г.1Minecraft: 1.21.9forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.21.8)
1.2.0-snapshot.8+1.21.8-forge4 июл. 2026 г.4Minecraft: 1.21.8forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.21.7)
1.2.0-snapshot.8+1.21.7-forge4 июл. 2026 г.2Minecraft: 1.21.7forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.21.6)
1.2.0-snapshot.8+1.21.6-forge4 июл. 2026 г.2Minecraft: 1.21.6forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.21.5)
1.2.0-snapshot.8+1.21.5-forge4 июл. 2026 г.4Minecraft: 1.21.5forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.21.4)
1.2.0-snapshot.8+1.21.4-forge4 июл. 2026 г.1Minecraft: 1.21.4forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.21.3)
1.2.0-snapshot.8+1.21.3-forge4 июл. 2026 г.1Minecraft: 1.21.3forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.21.2)
1.2.0-snapshot.8+1.21.2-forge4 июл. 2026 г.1Minecraft: 1.21.2forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.21.11)
1.2.0-snapshot.8+1.21.11-forge4 июл. 2026 г.8Minecraft: 1.21.11forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.21.10)
1.2.0-snapshot.8+1.21.10-forge4 июл. 2026 г.3Minecraft: 1.21.10forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.21.1)
1.2.0-snapshot.8+1.21.1-forge4 июл. 2026 г.6Minecraft: 1.21.1forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.20)
1.2.0-snapshot.8+1.20-forge4 июл. 2026 г.2Minecraft: 1.20forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.20.6)
1.2.0-snapshot.8+1.20.6-forge4 июл. 2026 г.2Minecraft: 1.20.6forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.20.5)
1.2.0-snapshot.8+1.20.5-forge4 июл. 2026 г.2Minecraft: 1.20.5forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.20.4)
1.2.0-snapshot.8+1.20.4-forge4 июл. 2026 г.5Minecraft: 1.20.4forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.20.3)
1.2.0-snapshot.8+1.20.3-forge4 июл. 2026 г.2Minecraft: 1.20.3forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.20.2)
1.2.0-snapshot.8+1.20.2-forge4 июл. 2026 г.3Minecraft: 1.20.2forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Forge 1.20.1)
1.2.0-snapshot.8+1.20.1-forge4 июл. 2026 г.40Minecraft: 1.20.1forge
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 26.2)
1.2.0-snapshot.8+26.2-fabric4 июл. 2026 г.98Minecraft: 26.2fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 26.1)
1.2.0-snapshot.8+26.1-fabric4 июл. 2026 г.4Minecraft: 26.1fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 26.1.2)
1.2.0-snapshot.8+26.1.2-fabric4 июл. 2026 г.46Minecraft: 26.1.2fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 26.1.1)
1.2.0-snapshot.8+26.1.1-fabric4 июл. 2026 г.5Minecraft: 26.1.1fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.21)
1.2.0-snapshot.8+1.21-fabric4 июл. 2026 г.6Minecraft: 1.21fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.21.9)
1.2.0-snapshot.8+1.21.9-fabric4 июл. 2026 г.2Minecraft: 1.21.9fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.21.8)
1.2.0-snapshot.8+1.21.8-fabric4 июл. 2026 г.5Minecraft: 1.21.8fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.21.7)
1.2.0-snapshot.8+1.21.7-fabric4 июл. 2026 г.3Minecraft: 1.21.7fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.21.6)
1.2.0-snapshot.8+1.21.6-fabric4 июл. 2026 г.2Minecraft: 1.21.6fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.21.5)
1.2.0-snapshot.8+1.21.5-fabric4 июл. 2026 г.4Minecraft: 1.21.5fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.21.4)
1.2.0-snapshot.8+1.21.4-fabric4 июл. 2026 г.4Minecraft: 1.21.4fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.21.3)
1.2.0-snapshot.8+1.21.3-fabric4 июл. 2026 г.4Minecraft: 1.21.3fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.21.2)
1.2.0-snapshot.8+1.21.2-fabric4 июл. 2026 г.3Minecraft: 1.21.2fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.21.11)
1.2.0-snapshot.8+1.21.11-fabric4 июл. 2026 г.38Minecraft: 1.21.11fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.21.10)
1.2.0-snapshot.8+1.21.10-fabric4 июл. 2026 г.6Minecraft: 1.21.10fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.21.1)
1.2.0-snapshot.8+1.21.1-fabric4 июл. 2026 г.7Minecraft: 1.21.1fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.20)
1.2.0-snapshot.8+1.20-fabric4 июл. 2026 г.5Minecraft: 1.20fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.20.6)
1.2.0-snapshot.8+1.20.6-fabric4 июл. 2026 г.2Minecraft: 1.20.6fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.20.5)
1.2.0-snapshot.8+1.20.5-fabric4 июл. 2026 г.1Minecraft: 1.20.5fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.20.4)
1.2.0-snapshot.8+1.20.4-fabric4 июл. 2026 г.2Minecraft: 1.20.4fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.20.3)
1.2.0-snapshot.8+1.20.3-fabric4 июл. 2026 г.4Minecraft: 1.20.3fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.20.2)
1.2.0-snapshot.8+1.20.2-fabric4 июл. 2026 г.3Minecraft: 1.20.2fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.8 (Fabric 1.20.1)
1.2.0-snapshot.8+1.20.1-fabric4 июл. 2026 г.7Minecraft: 1.20.1fabric
Что нового
The three remaining 1.2.0 pillars landed, plus a full in-game acceptance pass for Apollo's Enchantment Rebalance on Fabric 26.2 (#119). **§A** the pre-26.1 Fabric acceptance pass: real 1.16.5 Serilum mods run clean through full server start on a 1.20.1 host (Text/Entity-field/Material bridges + a rebuilt constructor-to-factory pass). **§D** the 1.12.2 in-game layers (SRG member namespace, the pre-1.13 FML lifecycle idiom, a 1.20.1-target class-move table). **§B** the 26.2 render-API stand-ins (`MultiBufferSource`, `Tesselator`, `VertexFormat$Mode`). And **#119**: the anvil-crash phantom redirect, a systemic phantom-target safety net, and the blank-enchanted-books tooltip rename, all fixed and verified in-game.
### Added
- **26.2 render-API stand-ins (SS B): render-referencing mods now LOAD on 26.2.** 26.2 deleted `MultiBufferSource`/`MultiBufferSource$BufferSource` and `Tesselator`, and replaced the `VertexFormat$Mode` enum with `PrimitiveTopology` (all verified against the real 26.1.2 and 26.2 client jars), so a 26.1-translated mod referencing any of them died `NoClassDefFoundError` at class load on 26.2, even when the render path never ran. New per-mod-embedded stand-ins reproduce the exact 26.1 shapes: `BufferSource.getBuffer(renderType)` returns a REAL 26.2 `BufferBuilder` built from the RenderType's own `format()`/`primitiveTopology()`, and `Tesselator.getInstance().begin(...)` works the same way, so mod geometry code actually executes; the `endBatch` family is staged (warns once and drops the batch: frame submission through the new `SubmitNodeCollector.submitCustomGeometry` pipeline is the follow-up). `VertexFormat$Mode` is class-redirected to `PrimitiveTopology` (identical constants and public fields). Registered in the 26.1 to 26.2 layer on all loaders. Tested (`RenderBufferSyntheticsTest`).
- **1.12.2 SRG member namespace (SS D, #103/#108/#117).** Distributed 1.12.2 Forge mods reference members by the OLD SRG names (`func_NNNNN_x`/`field_NNNNN_x`), a different namespace from the modern `m_`/`f_` one, so the SRG remap never fired for them. A new bundled dictionary (`srg-1.12.2-to-mojang.tsv`, 7,736 entries) maps them to readable names: harvested from the MCPBot 1.12.2 stable CSVs and kept ONLY where the name exists in Mojang's official 1.20.1 mappings (a wrong rename is worse than none). The remapper's SRG branch now matches both eras, and the dictionary loader merges both files. Also fixed a latent core bug the new tests flushed out: an SRG-only registration built no remapper at all (masked before because real runs always carry class redirects). Tested (`Srg1122MemberRemapTest`).
- **1.12.2 class moves for 1.20.x hosts (SS D).** The 1.12.2 class-move table only existed with 26.1-target names, but the reported 1.12.2 hosts (#103/#108/#117) all run 1.20.1. A new validated variant (`forge-1.12.2-class-moves-1201.tsv`, 125 rows, every target checked against Mojang's official 1.20.1 mappings by `scripts/harvest-1.12.2-class-moves-1201.py`) loads on 1.20.x hosts instead, and additionally carries the families 26.1 removed but 1.20.x still has (`ItemSword -> SwordItem`, `EnumAction -> UseAnim`, `CreativeTabs -> CreativeModeTab`, tool/armor tiers, ...). The Block/Item Properties constructor bridge and the `Material` static-field nuller now apply on 1.20.x hosts too (verified: 1.20 already removed `Material`, and `Properties.of()`/`Item$Properties()` are present with the same shapes as 26.1). 1.21.x hosts get no table (none validated against those jars). Tested (`Forge1122ClassMovesTest`).
- **1.12.2 in-game FML lifecycle layer (SS D, #103/#108/#117).** A 1.12.2 mod's `@Mod(modid=...)` annotation shape is invisible to a modern loader (it reads `@Mod(value)`), and nothing ever calls its `@Mod.EventHandler` setup methods, so even a mod that LOADED did nothing. The upgrade pass rewrites the annotation to the modern shape and injects a constructor-time call into an embedded lifecycle bridge that constructs the legacy `FMLPreInitialization`/`FMLInitialization`/`FMLPostInitialization` event stand-ins (config-dir surface included: `getSuggestedConfigurationFile` yields `config/<modid>.cfg`) and reflectively invokes the mod's handlers in order. Active only when the 1.12.2 shim chain registered. Tested (`Forge1122LifecycleTest`, including a functional fire test).
- **Pre-26.1 Fabric bridge chain from the LIVE acceptance pass (SS A, #55).** Real 1.16.5 Fabric content mods (Serilum's Double Doors, Mineral Chance, Stack Refill plus their shared Collective library) were run on an actual 1.20.1 Fabric server and every crash in the chain was fixed at the transform level:
- **Pre-1.19 Text bridge.** The 1.19 chat rework removed the `TranslatableText`/`LiteralText` constructors; `new TranslatableText("key")` died `NoSuchMethodError`. Constructor-to-factory redirects target `Text.translatable`/`Text.literal` (plus the with-args overload), registered with the InterfaceMethodref form (`Text` is an interface on 1.19+: a plain-Methodref INVOKESTATIC dies `IncompatibleClassChangeError`), and both old classes class-redirect to `MutableText` so the mod's downstream fluent calls (`formatted`, `setStyle`, `append`: same intermediary ids on `MutableText` since 1.16, verified on the 1.20.1 jar) type-check against the factory's return value.
- **Pre-1.17 Entity field bridge.** `Entity.onGround` went non-public in the 1.17 access cleanup; direct access died `IllegalAccessError`. GETFIELD/PUTFIELD rewrite to `isOnGround()`/`setOnGround(boolean)`.
- **Pre-1.20 Material bridge.** MC 1.20 deleted the Material system, and ONE `List<Material>` in a `<clinit>` (Collective's `GlobalVariables`) took the whole class down with `NoClassDefFoundError`. `class_3614` class-redirects to a shipped `MaterialPolyfill` (44 distinct constants under their exact 1.16.5 intermediary field ids, harvested from the official intermediary mappings), and `BlockState.getMaterial()` devirtualizes to a staged `fromState` (returns a sentinel matching no constant, so material-membership features quietly no-op instead of crashing).
- All three bridges are host-introspecting (probe with `Class.forName(..., false, ...)`, never initialize): they register only where the host actually lost the API, so 1.16.x-1.18.x hosts are untouched. Tested (`Pre1_17EntityFieldBridgeTest`, `Pre1_20MaterialBridgeTest`, plus the real failing Collective class as a gated repro).
- **Also fixed while here:** the fuzzy method resolver never activated on pre-26.1 Fabric hosts (its MC-jar probe only knew the Mojang-named `SharedConstants`; intermediary hosts need the `class_310`/`MinecraftServer` probes), and `Fabric_1_16_5_to_1_17` carried four 26.x-only tick-event renames (`*WorldTick` to `*LevelTick`) that broke the very hosts the shim targets; the correct copies live in the 26.1 shim.
### Fixed
- **1.12.2 Forge mods no longer die "mods.toml missing metadata for modid null" on 1.20.1+ (#120, Betweenlands / Scape and Run Parasites).** A 1.12.2 `@Mod` annotation is `@Mod(modid="x", name=..., version=..., dependencies=...)`; modern Forge's `@Mod` has ONLY `value()` (which IS the modid), so it read `value()` = null from the old shape and aborted mod loading with a null modid. The `Forge1122LifecycleSynthetics` `@Mod` modernization (added this cycle) collapses the whole legacy annotation to `@Mod(value=modid)` so Forge reads a real id. Verified on the real `SRPMain` class from Scape and Run Parasites (`@Mod(modid="srparasites", ...)` -> `@Mod(value="srparasites")`). The runtime Forge path already activated the pass via the 1.12.2 shim chain; the CLI transform now activates it too (at startup, self-gating on the old annotation shape so it never touches modern mods), so pre-transformed jars get the same fix. Tested (`Forge1122LifecycleTest`, incl. the full 4-element annotation shape).
- **Custom `ClientTooltipComponent` renderers now draw on 26.1+ (Apollo's Enchantment Rebalance blank enchanted books, #119).** 26.1 renamed `ClientTooltipComponent.renderText`/`renderImage` to `extractText`/`extractImage` (the params take `GuiGraphics`, itself renamed to `GuiGraphicsExtractor`). The intermediary->Mojang table is 1.21.4-based and mapped those two intermediary ids (`method_32665`/`method_32666`) to the OLD names, so a distributed 1.21.x Fabric mod's overrides were renamed to `renderText`/`renderImage` and no longer overrode the interface: the vanilla empty default methods ran, and the mod's custom tooltip drew nothing (AER's enchanted books showed no enchantment lines, even though the enchantments themselves applied and functioned). Corrected the two id mappings to the 26.1 names; the fix is id-scoped, so the four unrelated `renderText` methods are untouched. Tested (`ClientTooltipRenderRenameTest`, both the mapping and a transformed override). This is one instance of a broader class: the 1.21.4-based intermediary table carries stale names for any method renamed between 1.21.4 and 26.1, which breaks overrides on a 26.1+ host; more such renames may need the same correction as they surface.
- **Apollo's Enchantment Rebalance no longer crashes the anvil on Fabric 26.2 (#119).** The mod's anvil `createResult` calls `Enchantment.getMaxLevel()`, which Retromod was rewriting to a `com/retromod/shim/*/embedded/EnchantmentShim` that was never written (a phantom target): the anvil combined an item + book, the rewritten call resolved, and it died `NoClassDefFoundError` on first use. Three compounding faults, all fixed:
- **The Fabric transform path had no loader-type filter.** `RetromodPreLaunch` registered EVERY version shim regardless of `getModLoaderType()`, unlike the other three entry points. So a NeoForge shim's Mojang-named redirect (`NeoForge_1_20_6_to_1_21`) fired on a Fabric mod once the intermediary->Mojang harvest made the names match. It now applies the same `{fabric, common}` filter the NeoForge/Forge/onInitialize paths already use, which closes the entire class of cross-loader phantom bites on Fabric (NeoForge/Forge enchantment, ItemStack-NBT, DamageSource, Material, Tag redirects, etc.).
- **The redirects were wrong regardless.** `Enchantment.getMaxLevel()`/`getMinLevel()` never left the API (present 1.21 through 26.2, backed by the data-driven definition); redirecting them was pointless. Both the NeoForge and the Yarn-keyed Fabric copies (dead code per the intermediary-namespace note) are deleted. `getRarity` was genuinely removed but can't be bridged by a call-site redirect alone (its `Rarity` return type is gone too), so it's deleted pending a proper synthetic bridge.
- **A phantom-target safety net.** The transformer now sweeps its redirect tables once, before the first transform, and drops any redirect whose `com/retromod/*` target is neither a registered synthetic nor a loadable class, logging one warning instead of letting it detonate at first use. This retroactively neutralizes several other never-written targets found across the shim tree (`FlatteningShim`, `SidedProxyShim`, a wrong-package `GameRegistryShim`, ...): the affected feature quietly no-ops instead of crashing whatever runs it. This also surfaced and fixed a latent bug where a redirect set containing only super-constructor + class redirects skipped the method-body visitor entirely (the 1.12.2 Block/Item `super(Material)`->`super(Properties)` bridge relied on an unrelated method redirect being present to stay active). The reported blank-enchanted-books symptom is a separate client mixin-apply matter still under investigation.
- **Constructor-to-factory rewriting no longer corrupts stack frames when the args contain branches (found live on Collective's `DuskConfig`).** The old deferral buffered ONE pending `NEW` and flushed it as soon as a nested plain `new` appeared, i.e. mid-expression: with a ternary in the constructor args the buffered NEW+DUP landed on one branch path only, ASM's frame computation failed (`Frame.merge` AIOOBE), and the silent COMPUTE_MAXS fallback then shipped the class with its ORIGINAL StackMapTable whose offsets the rewrite had invalidated: `ClassFormatError: StackMapTable format error: bad offset for Uninitialized` at load, taking the whole mod down. The machinery is now a proper deferral STACK flushed only at the `<init>` convergence point (branch-safe by construction, and nested redirect-eligible `new`s compose), it moved UPSTREAM of the class remapper into its own pass (`CtorRedirectPrePass`, so constructor redirects stay keyed on distinct pre-remap owners even when several old classes retype onto ONE new class, as the Text bridge needs), the fallback now logs the primary failure instead of swallowing it, and a fallback pass that made frame-invalidating rewrites refuses to ship the result (degrades to the untransformed class instead of a corrupt one). Regression-tested with synthesized pathological shapes (`CtorRedirectBranchedArgsTest`) plus the real failing class.
release
Retromod 1.2.0-snapshot.7 (NeoForge 26.2)
1.2.0-snapshot.7+26.2-neoforge2 июл. 2026 г.13Minecraft: 26.2neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 26.1.2)
1.2.0-snapshot.7+26.1.2-neoforge2 июл. 2026 г.11Minecraft: 26.1.2neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.21)
1.2.0-snapshot.7+1.21-neoforge2 июл. 2026 г.2Minecraft: 1.21neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.21.9)
1.2.0-snapshot.7+1.21.9-neoforge2 июл. 2026 г.4Minecraft: 1.21.9neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.21.8)
1.2.0-snapshot.7+1.21.8-neoforge2 июл. 2026 г.3Minecraft: 1.21.8neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.21.7)
1.2.0-snapshot.7+1.21.7-neoforge2 июл. 2026 г.2Minecraft: 1.21.7neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.21.6)
1.2.0-snapshot.7+1.21.6-neoforge2 июл. 2026 г.3Minecraft: 1.21.6neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.21.5)
1.2.0-snapshot.7+1.21.5-neoforge2 июл. 2026 г.1Minecraft: 1.21.5neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.21.4)
1.2.0-snapshot.7+1.21.4-neoforge2 июл. 2026 г.3Minecraft: 1.21.4neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.21.3)
1.2.0-snapshot.7+1.21.3-neoforge2 июл. 2026 г.1Minecraft: 1.21.3neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.21.2)
1.2.0-snapshot.7+1.21.2-neoforge2 июл. 2026 г.3Minecraft: 1.21.2neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.21.11)
1.2.0-snapshot.7+1.21.11-nf2 июл. 2026 г.6Minecraft: 1.21.11neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.21.10)
1.2.0-snapshot.7+1.21.10-nf2 июл. 2026 г.5Minecraft: 1.21.10neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.21.1)
1.2.0-snapshot.7+1.21.1-neoforge2 июл. 2026 г.40Minecraft: 1.21.1neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.20.6)
1.2.0-snapshot.7+1.20.6-neoforge2 июл. 2026 г.4Minecraft: 1.20.6neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.20.5)
1.2.0-snapshot.7+1.20.5-neoforge2 июл. 2026 г.1Minecraft: 1.20.5neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.20.4)
1.2.0-snapshot.7+1.20.4-neoforge2 июл. 2026 г.1Minecraft: 1.20.4neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.20.3)
1.2.0-snapshot.7+1.20.3-neoforge2 июл. 2026 г.3Minecraft: 1.20.3neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.20.2)
1.2.0-snapshot.7+1.20.2-neoforge2 июл. 2026 г.1Minecraft: 1.20.2neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (NeoForge 1.20.1)
1.2.0-snapshot.7+1.20.1-neoforge2 июл. 2026 г.5Minecraft: 1.20.1neoforge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 26.2)
1.2.0-snapshot.7+26.2-forge2 июл. 2026 г.12Minecraft: 26.2forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 26.1)
1.2.0-snapshot.7+26.1-forge2 июл. 2026 г.4Minecraft: 26.1forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 26.1.2)
1.2.0-snapshot.7+26.1.2-forge2 июл. 2026 г.3Minecraft: 26.1.2forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 26.1.1)
1.2.0-snapshot.7+26.1.1-forge2 июл. 2026 г.5Minecraft: 26.1.1forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.21)
1.2.0-snapshot.7+1.21-forge2 июл. 2026 г.2Minecraft: 1.21forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.21.9)
1.2.0-snapshot.7+1.21.9-forge2 июл. 2026 г.1Minecraft: 1.21.9forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.21.8)
1.2.0-snapshot.7+1.21.8-forge2 июл. 2026 г.3Minecraft: 1.21.8forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.21.7)
1.2.0-snapshot.7+1.21.7-forge2 июл. 2026 г.2Minecraft: 1.21.7forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.21.6)
1.2.0-snapshot.7+1.21.6-forge2 июл. 2026 г.4Minecraft: 1.21.6forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.21.5)
1.2.0-snapshot.7+1.21.5-forge2 июл. 2026 г.1Minecraft: 1.21.5forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.21.4)
1.2.0-snapshot.7+1.21.4-forge2 июл. 2026 г.1Minecraft: 1.21.4forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.21.3)
1.2.0-snapshot.7+1.21.3-forge2 июл. 2026 г.1Minecraft: 1.21.3forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.21.2)
1.2.0-snapshot.7+1.21.2-forge2 июл. 2026 г.2Minecraft: 1.21.2forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.21.11)
1.2.0-snapshot.7+1.21.11-forge2 июл. 2026 г.1Minecraft: 1.21.11forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.21.10)
1.2.0-snapshot.7+1.21.10-forge2 июл. 2026 г.3Minecraft: 1.21.10forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.21.1)
1.2.0-snapshot.7+1.21.1-forge2 июл. 2026 г.5Minecraft: 1.21.1forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.20)
1.2.0-snapshot.7+1.20-forge2 июл. 2026 г.1Minecraft: 1.20forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.20.6)
1.2.0-snapshot.7+1.20.6-forge2 июл. 2026 г.2Minecraft: 1.20.6forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.20.5)
1.2.0-snapshot.7+1.20.5-forge2 июл. 2026 г.4Minecraft: 1.20.5forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.20.4)
1.2.0-snapshot.7+1.20.4-forge2 июл. 2026 г.3Minecraft: 1.20.4forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.20.3)
1.2.0-snapshot.7+1.20.3-forge2 июл. 2026 г.3Minecraft: 1.20.3forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.20.2)
1.2.0-snapshot.7+1.20.2-forge2 июл. 2026 г.3Minecraft: 1.20.2forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Forge 1.20.1)
1.2.0-snapshot.7+1.20.1-forge2 июл. 2026 г.31Minecraft: 1.20.1forge
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 26.2)
1.2.0-snapshot.7+26.2-fabric2 июл. 2026 г.91Minecraft: 26.2fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 26.1)
1.2.0-snapshot.7+26.1-fabric2 июл. 2026 г.3Minecraft: 26.1fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 26.1.2)
1.2.0-snapshot.7+26.1.2-fabric2 июл. 2026 г.30Minecraft: 26.1.2fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 26.1.1)
1.2.0-snapshot.7+26.1.1-fabric2 июл. 2026 г.4Minecraft: 26.1.1fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.21)
1.2.0-snapshot.7+1.21-fabric2 июл. 2026 г.3Minecraft: 1.21fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.21.9)
1.2.0-snapshot.7+1.21.9-fabric2 июл. 2026 г.4Minecraft: 1.21.9fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.21.8)
1.2.0-snapshot.7+1.21.8-fabric2 июл. 2026 г.1Minecraft: 1.21.8fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.21.7)
1.2.0-snapshot.7+1.21.7-fabric2 июл. 2026 г.4Minecraft: 1.21.7fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.21.6)
1.2.0-snapshot.7+1.21.6-fabric2 июл. 2026 г.2Minecraft: 1.21.6fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.21.5)
1.2.0-snapshot.7+1.21.5-fabric2 июл. 2026 г.3Minecraft: 1.21.5fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.21.4)
1.2.0-snapshot.7+1.21.4-fabric2 июл. 2026 г.5Minecraft: 1.21.4fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.21.3)
1.2.0-snapshot.7+1.21.3-fabric2 июл. 2026 г.2Minecraft: 1.21.3fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.21.2)
1.2.0-snapshot.7+1.21.2-fabric2 июл. 2026 г.2Minecraft: 1.21.2fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.21.11)
1.2.0-snapshot.7+1.21.11-fabric2 июл. 2026 г.18Minecraft: 1.21.11fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.21.10)
1.2.0-snapshot.7+1.21.10-fabric2 июл. 2026 г.3Minecraft: 1.21.10fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.21.1)
1.2.0-snapshot.7+1.21.1-fabric2 июл. 2026 г.12Minecraft: 1.21.1fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.20)
1.2.0-snapshot.7+1.20-fabric2 июл. 2026 г.1Minecraft: 1.20fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.20.6)
1.2.0-snapshot.7+1.20.6-fabric2 июл. 2026 г.3Minecraft: 1.20.6fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.20.5)
1.2.0-snapshot.7+1.20.5-fabric2 июл. 2026 г.2Minecraft: 1.20.5fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.20.4)
1.2.0-snapshot.7+1.20.4-fabric2 июл. 2026 г.2Minecraft: 1.20.4fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.20.3)
1.2.0-snapshot.7+1.20.3-fabric2 июл. 2026 г.3Minecraft: 1.20.3fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.20.2)
1.2.0-snapshot.7+1.20.2-fabric2 июл. 2026 г.2Minecraft: 1.20.2fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.7 (Fabric 1.20.1)
1.2.0-snapshot.7+1.20.1-fabric2 июл. 2026 г.8Minecraft: 1.20.1fabric
Что нового
The "big update" cycle: the Forge-family deep work (Forge -> NeoForge migration, and the Forge 26.2 EventBus 6->7 break), the 1.12.2 in-game stack, and the 26.x worldgen/datapack chain taken from "registers nothing" all the way to structures generating in-world. Two milestones this cycle. **(1)** The acceptance set of small Forge content mods (Macaw's Roofs / Trapdoors / Bridges), built for **Forge 1.21.1**, now **fully loads on NeoForge 26.2** - verified in-game (all three scan, construct, register their blocks/items, their resources reload, the sound engine starts, and the client reaches the main menu with no crash). **(2)** YUNG's API + Better Dungeons (**Fabric 1.21.1**) now register AND generate on a **26.2 dedicated server**: `/locate` finds a YUNG structure and `/place` builds one in-world with zero registry, parse, or generation errors.
### Added
- **Forge -> NeoForge: the construct + register spine.** A Forge 1.20.1 content mod now gets scanned, constructs, and registers its content on NeoForge 26.2. The pieces (each target verified against the NeoForge 26.2 jar):
- **`FMLJavaModLoadingContext` bridge (#85/#115).** `get().getModEventBus()` is the first line of nearly every Forge `@Mod` constructor, and NeoForge deleted the class. Retromod embeds a per-mod synthetic bridge that delegates to `ModLoadingContext.get().getActiveContainer().getEventBus()`. The Forge -> NeoForge redirect that lets the embedder match the reference now lives on the API-shim path, so it applies on **both** the offline CLI/AOT batch and the live runtime (previously it was on a runtime-only shim the offline chain skipped). Verified in-game.
- **Registry-id bridge (#87).** MC 1.21.3+ requires the registry id stamped on a Block/Item `Properties` before the object is built, but Forge's `DeferredRegister.register(String, Supplier)` constructs it inside the supplier with no id (so `RegisterEvent` fails "Block/Item id not set"). A per-mod synthetic threads the id through a thread-local: registration is rerouted through the id-aware `register(String, Function)` overload, and the `Properties` factories (`of`/`ofFullCopy`/`ofLegacyCopy`, `new Item.Properties()`) stamp `setId` from it. Verified in-game (blocks/items register).
- **Extension-interface + lifecycle/server event migration.** `IForgeItem`/`IForgeBlock`/`IForgeEntity`/`IForgeBlockEntity` -> NeoForge's `I*Extension`; `FMLCommonSetupEvent`, `FMLClientSetupEvent` (-> `net/neoforged/fml/event/lifecycle/*`), `ServerStartingEvent` (-> the neoforge event package). These join the shim's existing registry/capability/config/network redirects.
- **26.2 `ColorCollection` consolidation (all loaders).** 26.2 folded each per-color family (16 colors) of `Blocks`/`Items` constants into a single `ColorCollection` field and removed the per-color statics, so a 1.21.x mod reading `Blocks.BLACK_WOOL` (or `Items.WHITE_CARPET`, ...) hits `NoSuchFieldError`. The 26.1->26.2 shim already rewrote four block families to `<field>.pick(DyeColor.<COLOR>)` accessors; this extends it to **all 14 block families and 14 item families** (wool, carpet, concrete, concrete_powder, stained glass + panes, beds, banners, wall banners, glazed + dyed terracotta, shulker boxes, candles, dyes, bundles), each verified against the 26.2 jar (the collection field name is irregular, e.g. `WOOL` vs `DYED_CANDLE`) with the correct Block-vs-Item cast. Shared across Fabric/NeoForge/Forge. Tested (`DyedBlockAccessorTest`); it was the last blocker to the Macaw's loading in-game.
- **Forge 26.2 EventBus 6 -> 7 bridge (#85) - old Forge mods now load on Forge 26.2.** Forge 26.2 (65.x) replaced EventBus 6 with EventBus 7: `IEventBus` and the old `Event` base are gone (`BusGroup` + per-type `EventBus<T>` instead), so every old Forge mod died at construction. A synthetic `LegacyEventBus` interface (so the mod's interface call sites stay verifiable) bridges the old idiom onto `BusGroup`: `getModEventBus()` -> `getModBusGroup()` wrap, `DeferredRegister.register(bus)` -> `register(BusGroup)`, `MinecraftForge.EVENT_BUS` -> a `BusGroup.DEFAULT`-backed bridge, old `@SubscribeEvent` -> EventBus 7's annotation, plus workarounds for EventBus 7's stricter policies (single-listener classes are wired to their event's own `BUS` reflectively; `@Mod.EventBusSubscriber` classes EventBus 7 would reject are skipped with the EventBus 6 semantics preserved). Also fixed along the way, each hit in-game: Forge 26.2 RESTORED the `IForgeItem`-style names (the 26.1 "I-drop" rename is now version-gated), a Forge-flavored registry-id bridge (Forge has no id-aware register overload; the id comes from `RegistryObject.getKey()`), and the synthetic embedder now embeds transitively (a bridge referencing a helper synthetic no longer dangles). Verified in-game: Macaw's Bridges (Forge 1.21.1) constructs, registers its content, and reaches the menu on Forge 26.2.
- **EventBus 7: lambda setup listeners + priority overloads now bind (#101).** The final piece of the EventBus 6->7 bridge. `bus.addListener(this::onSetup)` compiles to a `LambdaMetafactory` invokedynamic followed by the call; EventBus 6 read the event type from the lambda's constant pool, EventBus 7 cannot. Retromod now recovers it at transform time: a call fed directly by a `Consumer`-producing indy is retargeted to a typed helper with the lambda's reified event type appended as a `Class` constant, so it resolves the event's own `BUS` exactly (a non-adjacent/stored Consumer falls through to runtime generics recovery, as before). This un-inerts the lifecycle-setup callbacks the previous build soft-failed (verified: Macaw's Bridges' `FMLCommonSetupEvent`/`FMLClientSetupEvent` listeners now bind). Also added: EventBus 6's deleted `EventPriority` enum is supplied as a real-enum stand-in (constants, `values()`/`valueOf()`, enum-switch all work) carrying the matching EventBus 7 `Priority` byte, and the bridge gained the priority-carrying `addListener` overloads (`(EventPriority, Consumer)`, `(EventPriority, boolean, Consumer)`, `(EventPriority, boolean, Class, Consumer)`). Tested (`ForgeEventBusBridgeTest`).
- **Forge entry point never loaded the polyfills.** `RetromodForge` was missing the `PolyfillRegistry` wiring that Fabric, NeoForge, and the CLI all have, so all removed-API polyfills (e.g. the #24 `DirectionProperty` bridge) silently never applied on Forge hosts - surfaced on Forge 26.2 as `NoSuchMethodError: EnumProperty.create(String, Direction[])` killing block class-init. Now loaded (with the NeoForge-specific category off, as on NeoForge).
- **Sinytra Connector techniques (clean-room, credited).** A descriptor-qualified member-name fallback (correctly disambiguates overloaded members), an AccessWidener -> NeoForge AccessTransformer converter, and an **offline Forge -> NeoForge mode** (`--target-loader neoforge`) so the migration runs from the CLI without a live NeoForge host.
- **Fuzzy-resolver report for offline bridge authoring (`config/retromod/fuzzy-report.tsv`).** The fuzzy resolver's near-miss band (50-84% confidence: "this looks like a rename but I won't auto-apply it") is exactly the data needed to hand-author a precise shim bridge from a real failing mod, but scraping it out of an interleaved launch log is painful. Retromod now also writes each outcome as a machine-readable TSV row (one per distinct unresolved reference, fresh per run): `NEAR` rows are the bridge-authoring backlog, `AUTO` rows are applied auto-redirects to review, `SUPPRESSED` rows are type-incompatible matches that need a hand-written adapter. Reporting failures never break a transform. Tested (`FuzzyMatchReportTest`).
- **1.12.2 metadata generation: `mcmod.info` -> `mods.toml` (#79).** A pre-1.13 Forge mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so modern Forge/NeoForge never scanned it and snapshot.6's class-move + ctor bridges never ran. `ForgeModTransformer` now synthesizes a `mods.toml` from `mcmod.info` (id/version/name/description extracted by regex, ranges relaxed to `[1,)`), which `promoteToNeoForgeToml` then renames to `neoforge.mods.toml` on a NeoForge host. This is the **first 1.12.2 in-game prerequisite** found in snapshot.6 testing: the mod now gets scanned (the SRG member + registry-idiom layers are still ahead). Tested (`McmodInfoTomlGenTest`).
### Fixed
- **Post-review hardening (multi-agent code + security review of this cycle).** Thirteen adversarially-verified findings fixed, the notable ones: redirects to interface synthetics now force the InterfaceMethodref flag (a pre-1.19.3 mod's `Registry.register` call would otherwise die `IncompatibleClassChangeError` at its first registration on 26.2 targets); the 26.x became-interface opcode fixes (`IntProvider`/`FloatProvider`) are host-gated so pre-26.x translations aren't corrupted; the Fabric entry point now applies the standard "shim target must not exceed host" gate; `ForgeCorePolyfill` no longer class-redirects a LIVE `MinecraftForge` on real Forge hosts; the CLI syncs its default target version into the shim gates (previously only `--target` runs did); plus zip-slip/zip-bomb guards on the AOT paths, atomic jar rewrites in the synthetic embedder, and the client item-definition synthesis now also covers 1.21.4-1.21.11 targets.
- **Rewritten jars now keep their ZIP directory entries - fixes silently-broken classpath scanning.** Gradle-built mod jars carry an entry per directory; Retromod's tree-rezip paths emitted only file entries. A jar classloader resolves package resources (`ClassLoader.getResources("com/example")`) only from directory entries, so Reflections/ClassGraph-style scanners found NOTHING in a transformed jar - with no exception anywhere. Found on a headless Fabric 26.2 dedicated server: YungsApi's Reflections-based `@AutoRegister` system scanned "0 urls, producing 0 keys", none of its worldgen types registered, and every YUNG structure failed datapack load as "Unknown registry key" (original jar: 48 directory entries; transformed: 0). Fixed on every jar-writing path (runtime Fabric/Forge/Quilt, AOT, synthetic embedder); verified on the server: the scan now finds the package and ALL worldgen types register. This was very likely the root cause of the long-standing "worldgen-heavy mods stall on 26.x" pattern.
- **Classes no redirect touches now ship byte-identical.** The transform loop re-serialized every class (fresh constant pool + recomputed stack frames) even when nothing matched. For a class whose type hierarchy isn't loadable at transform time (a JiJ'd library), frame recomputation merges unknown sister types to `java/lang/Object` and the rewritten class fails JVM verification at runtime - YungsApi's bundled javassist died with `VerifyError` in `ConstPool.readOne`, taking the whole registration path with it. The transform now compares pass 1 against an identity re-serialization and ships the ORIGINAL bytes when nothing semantically changed (cost-neutral: it replaces the old stability pass). Tested (`UntouchedClassPreservationTest`).
- **`IntProvider`/`FloatProvider` became interfaces on 26.x, and their static range-codec factories moved.** `INVOKEVIRTUAL` on them is now auto-corrected to `INVOKEINTERFACE` (and, on the fast path too, static calls get the `InterfaceMethodref` flag - a static method on an interface dies with `IncompatibleClassChangeError` otherwise). The removed `IntProvider.codec(min,max)` / `FloatProvider.codec(min,max)` factories are bridged to the 26.x companion dispatch codecs (`IntProviders.CODEC` / `FloatProviders.CODEC`; the removed factories only added range validation on top of the same codec). All hit in-game by YUNG's Jigsaw structure codec at datapack load on the 26.2 server.
- **Worldgen milestone: YUNG's API + Better Dungeons (Fabric 1.21.1) fully load on a 26.2 dedicated server.** On top of the directory-entry and byte-identity fixes above, three more 26.x breaks were bridged, each captured live on the headless server: (1) the `STRUCTURE_PROCESSOR` registry now holds `MapCodec`s directly, so a 1.21.x mod's `StructureProcessorType` SAM-lambda registration broke every `processor_list` that referenced it - a new bridge intercepts `Registry.register` into that registry and converts the value via its old `codec()` SAM (registering the codec, returning the mod's original value so its fields still hold); (2) `StructureProcessor` itself became an interface, so processor subclasses died at class definition - a new superclass-rebase variant rewrites `extends` to `Object` + `implements`, including the `super()` constructor call; (3) `IntProvider`/`FloatProvider`'s static codec factories and constants moved to `IntProviders`/`FloatProviders` with identical signatures (plain owner redirects; also the `IntProvider.CODEC` miss in #114's gap report). Result: server reaches "Done" with zero registry errors and every YUNG worldgen registry bound.
- **Worldgen generation: YUNG structures now generate in-world on 26.2, not just register.** Past registration, three more 26.x breaks in the jigsaw generation path were bridged, each captured live on the server during `/place` and `/locate`: (1) `StructurePoolElement.getShuffledJigsawBlocks` still returns a `List`, but its elements are now `StructureTemplate$JigsawBlockInfo` wrappers (26.1) instead of `StructureBlockInfo`, so a 1.21.x caller's element cast died `ClassCastException` - a bridge unwraps each element via `JigsawBlockInfo.info()`; (2) `JigsawBlock.canAttach` was re-typed from `(StructureBlockInfo, StructureBlockInfo)` to `(JigsawBlockInfo, JigsawBlockInfo)` - a bridge wraps both args via `JigsawBlockInfo.of`; (3) MC renamed `Registry.getHolder`/`getHolderOrThrow` to `get`/`getOrThrow` (now inherited from `HolderGetter`), so a 1.21.1 mod's holder lookup during structure assembly died `NoSuchMethodError` - fixed as a same-owner rename. Result: `/locate structure` finds a YUNG structure and `/place structure` generates one in-world with no errors. Tested (`IsOverloadBridgeTest`).
- **The fuzzy resolver no longer rewrites a reference that already resolves.** The last-resort fuzzy matcher scored candidates even for calls that were already valid, and could pick a same-descriptor sibling declared directly on the owner over the real inherited method (an exact-class score beats a related-class one). Live failure: `Registry.get(ResourceKey)` (inherited from `HolderGetter`, returns an `Optional<Holder>`) was rewritten to the value getter `Registry.getOptional`, and YUNG's worldgen died `ClassCastException: StructureTemplatePool cannot be cast to Holder` at the first structure piece. Both fuzzy entry points now short-circuit when the reference is present (hierarchy-aware), so a working call is never touched. Tested (`FuzzyMatchReportTest`).
- **Placement crash: 26.1 removed the single-arg `is()` overloads.** `BlockState.is(Block)`/`is(TagKey)`, `ItemStack.is(Item)`/`is(TagKey)`, and all of `FluidState.is(...)` are gone on 26.1+ (verified against the real jars; only the Predicate-taking forms survive), so a 1.21.x mod dies with `NoSuchMethodError` at first use - e.g. Macaw's Bridges crashed the server tick loop placing a bridge (`Bridge_Block.onPlace`). A per-mod synthetic (`IsOverloadBridge`) now carries faithful static reimplementations of the removed overloads (via `getBlock()`/`getItem()`/`getType()` identity and `builtInRegistryHolder().is(...)` tag/holder/key checks), and old call sites are rewritten onto it from the 1.21.11 -> 26.1 shims on all three loaders. Tested (`IsOverloadBridgeTest`).
- **Invisible items: 1.21.4+ client item definitions are now synthesized.** MC 1.21.4 split "client item" definitions out of item models: every item id needs an `assets/<ns>/items/<id>.json` naming its model, and items without one render as the purple/black missing model (the checkerboard creative tab and the giant magenta held-item cube). Pre-1.21.4 mods only ship `models/item/*.json`, so ALL their items were invisible on 26.x (Macaw's Bridges: 146 item models, 0 definitions). The data migrator now generates the missing definitions on every transform path (runtime and CLI). Verified in-game: "Missing item model" warnings went from 400+ to 0. Tested (`ModDataMigratorTest`).
- **Retromod didn't initialize on NeoForge 26.2 (#90).** Declaring the mod-file-locator SPI made FancyModLoader claim the whole Retromod jar as an "early service" and then skip it for `@Mod` scanning, so `RetromodNeoForge` never constructed and the entire runtime transform was inert. Removed the service declaration from the jar (the locator class stays; `mods/Retromod/` CurseForge-export loading moves to a separate stub jar). Confirmed via a real GUI launch; this was the prerequisite for the Forge -> NeoForge work above to run at all.
- **`IForgeItem` -> `ForgeItem` shim conflict on NeoForge.** The Forge-26.1 rename shim (drop the "I", stay forge-packaged) was clobbering the NeoForge migration's `IForgeItem` -> `IItemExtension` (last-writer-wins across ServiceLoader shims), leaving a `net/minecraftforge/common/extensions/ForgeItem` reference that exists on no NeoForge classpath -> `NoClassDefFoundError` on a custom Item. The forge-extension renames are now gated to Forge hosts only.
- **`MinecraftForge.EVENT_BUS` `NoSuchFieldError` on NeoForge.** A polyfill class-redirected `MinecraftForge` to a capabilities shim with no `EVENT_BUS` field, clobbering the migration's correct `MinecraftForge` -> `NeoForge` (which has the field). Gated off on NeoForge so a Forge mod's `MinecraftForge.EVENT_BUS.register(this)` resolves.
- **Apollo's Enchantment Rebalance produced a broken datapack on 26.x (#114).** The mod's entrypoint died before registering its custom enchantment effect/condition types, and its data then failed to load, so the datapack looked broken. Three distinct 26.x breaks were bridged: (1) `ConditionalEffect.codec(Codec)` dropped its `ContextKeySet` validation argument (an arg-dropping redirect handles the old two-arg call); (2) the loot-type registries were MapCodec-ized and the `LootItemFunctionType`/`LootItemConditionType` wrappers deleted (wrapper stand-ins plus registration-time value conversion, alongside the same treatment for the structure/density registries); (3) 26.x namespaces the keys in entity predicate JSON (`"type"` -> `"minecraft:entity_type"`, `flags`/`location`/`movement`/... -> `minecraft:*`), so an old advancement/loot predicate silently failed to match - the data migrator now rewrites those keys (subset-guarded so it only touches genuine entity predicates, recursing into `vehicle`/`passenger`/`targeted_entity` and advancement predicate slots). Two new intermediary IDs (`TooltipDisplay`, `Weapon`) were appended for the same mod. Result: 0 errors, custom types register, datapack loads. Tested (`ModDataMigratorTest`).
- **The AOT cache now truly auto-clears when Retromod changes.** The per-jar cache entries already carried version + self-hash headers (checked on cache hits), but two paths never validated at all: the Hybrid engine's per-class preload served every `.class` file in `config/retromod/aot-cache/` blindly, and the full-AOT cache (`retromod-cache/full-aot/`) had no version check either, so after updating Retromod both could keep serving the PREVIOUS build's transforms until the folder was deleted by hand. A generation stamp (`.cache-stamp`, Retromod version + self-hash of its own classes) now guards every cache directory: when the owning build differs, the whole cache is wiped and re-stamped automatically at startup. Nobody needs to remember `rm -rf config/retromod/aot-cache/` anymore. Tested (`AotCacheStampTest`).
- **CLI transform: the "all shims" fallback didn't embed synthetics.** When a mod's source MC version can't be detected, the CLI applies every shim and transforms, but that branch never called the synthetic embedder - so a jar whose references were rewritten onto a deleted-class bridge (e.g. the EventBus 7 `LegacyEventBus`) shipped with dangling references (`NoClassDefFoundError` at load). It now embeds on that path too, matching the version-detected path.
release
Retromod 1.2.0-snapshot.6 (NeoForge 26.2)
1.2.0-snapshot.6+26.2-neoforge30 июн. 2026 г.27Minecraft: 26.2neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 26.1.2)
1.2.0-snapshot.6+26.1.2-neoforge30 июн. 2026 г.12Minecraft: 26.1.2neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.21)
1.2.0-snapshot.6+1.21-neoforge30 июн. 2026 г.4Minecraft: 1.21neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.21.9)
1.2.0-snapshot.6+1.21.9-neoforge30 июн. 2026 г.4Minecraft: 1.21.9neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.21.8)
1.2.0-snapshot.6+1.21.8-neoforge30 июн. 2026 г.7Minecraft: 1.21.8neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.21.7)
1.2.0-snapshot.6+1.21.7-neoforge30 июн. 2026 г.4Minecraft: 1.21.7neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.21.6)
1.2.0-snapshot.6+1.21.6-neoforge30 июн. 2026 г.4Minecraft: 1.21.6neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.21.5)
1.2.0-snapshot.6+1.21.5-neoforge30 июн. 2026 г.3Minecraft: 1.21.5neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.21.4)
1.2.0-snapshot.6+1.21.4-neoforge30 июн. 2026 г.2Minecraft: 1.21.4neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.21.3)
1.2.0-snapshot.6+1.21.3-neoforge30 июн. 2026 г.3Minecraft: 1.21.3neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.21.2)
1.2.0-snapshot.6+1.21.2-neoforge30 июн. 2026 г.3Minecraft: 1.21.2neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.21.11)
1.2.0-snapshot.6+1.21.11-nf30 июн. 2026 г.10Minecraft: 1.21.11neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.21.10)
1.2.0-snapshot.6+1.21.10-nf30 июн. 2026 г.4Minecraft: 1.21.10neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.21.1)
1.2.0-snapshot.6+1.21.1-neoforge30 июн. 2026 г.197Minecraft: 1.21.1neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.20.6)
1.2.0-snapshot.6+1.20.6-neoforge30 июн. 2026 г.6Minecraft: 1.20.6neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.20.5)
1.2.0-snapshot.6+1.20.5-neoforge30 июн. 2026 г.5Minecraft: 1.20.5neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.20.4)
1.2.0-snapshot.6+1.20.4-neoforge30 июн. 2026 г.5Minecraft: 1.20.4neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.20.3)
1.2.0-snapshot.6+1.20.3-neoforge30 июн. 2026 г.6Minecraft: 1.20.3neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.20.2)
1.2.0-snapshot.6+1.20.2-neoforge30 июн. 2026 г.4Minecraft: 1.20.2neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (NeoForge 1.20.1)
1.2.0-snapshot.6+1.20.1-neoforge30 июн. 2026 г.5Minecraft: 1.20.1neoforge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 26.2)
1.2.0-snapshot.6+26.2-forge30 июн. 2026 г.10Minecraft: 26.2forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 26.1)
1.2.0-snapshot.6+26.1-forge30 июн. 2026 г.5Minecraft: 26.1forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 26.1.2)
1.2.0-snapshot.6+26.1.2-forge30 июн. 2026 г.9Minecraft: 26.1.2forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 26.1.1)
1.2.0-snapshot.6+26.1.1-forge30 июн. 2026 г.6Minecraft: 26.1.1forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.21)
1.2.0-snapshot.6+1.21-forge30 июн. 2026 г.5Minecraft: 1.21forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.21.9)
1.2.0-snapshot.6+1.21.9-forge30 июн. 2026 г.4Minecraft: 1.21.9forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.21.8)
1.2.0-snapshot.6+1.21.8-forge30 июн. 2026 г.4Minecraft: 1.21.8forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.21.7)
1.2.0-snapshot.6+1.21.7-forge30 июн. 2026 г.4Minecraft: 1.21.7forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.21.6)
1.2.0-snapshot.6+1.21.6-forge30 июн. 2026 г.3Minecraft: 1.21.6forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.21.5)
1.2.0-snapshot.6+1.21.5-forge30 июн. 2026 г.5Minecraft: 1.21.5forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.21.4)
1.2.0-snapshot.6+1.21.4-forge30 июн. 2026 г.4Minecraft: 1.21.4forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.21.3)
1.2.0-snapshot.6+1.21.3-forge30 июн. 2026 г.3Minecraft: 1.21.3forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.21.2)
1.2.0-snapshot.6+1.21.2-forge30 июн. 2026 г.2Minecraft: 1.21.2forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.21.11)
1.2.0-snapshot.6+1.21.11-forge30 июн. 2026 г.5Minecraft: 1.21.11forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.21.10)
1.2.0-snapshot.6+1.21.10-forge30 июн. 2026 г.4Minecraft: 1.21.10forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.21.1)
1.2.0-snapshot.6+1.21.1-forge30 июн. 2026 г.4Minecraft: 1.21.1forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.20)
1.2.0-snapshot.6+1.20-forge30 июн. 2026 г.4Minecraft: 1.20forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.20.6)
1.2.0-snapshot.6+1.20.6-forge30 июн. 2026 г.2Minecraft: 1.20.6forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.20.5)
1.2.0-snapshot.6+1.20.5-forge30 июн. 2026 г.4Minecraft: 1.20.5forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.20.4)
1.2.0-snapshot.6+1.20.4-forge30 июн. 2026 г.4Minecraft: 1.20.4forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.20.3)
1.2.0-snapshot.6+1.20.3-forge30 июн. 2026 г.6Minecraft: 1.20.3forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.20.2)
1.2.0-snapshot.6+1.20.2-forge30 июн. 2026 г.3Minecraft: 1.20.2forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Forge 1.20.1)
1.2.0-snapshot.6+1.20.1-forge30 июн. 2026 г.28Minecraft: 1.20.1forge
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 26.2)
1.2.0-snapshot.6+26.2-fabric30 июн. 2026 г.76Minecraft: 26.2fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 26.1)
1.2.0-snapshot.6+26.1-fabric30 июн. 2026 г.5Minecraft: 26.1fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 26.1.2)
1.2.0-snapshot.6+26.1.2-fabric30 июн. 2026 г.42Minecraft: 26.1.2fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 26.1.1)
1.2.0-snapshot.6+26.1.1-fabric30 июн. 2026 г.4Minecraft: 26.1.1fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.21)
1.2.0-snapshot.6+1.21-fabric30 июн. 2026 г.5Minecraft: 1.21fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.21.9)
1.2.0-snapshot.6+1.21.9-fabric30 июн. 2026 г.3Minecraft: 1.21.9fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.21.8)
1.2.0-snapshot.6+1.21.8-fabric30 июн. 2026 г.3Minecraft: 1.21.8fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.21.7)
1.2.0-snapshot.6+1.21.7-fabric30 июн. 2026 г.2Minecraft: 1.21.7fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.21.6)
1.2.0-snapshot.6+1.21.6-fabric30 июн. 2026 г.4Minecraft: 1.21.6fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.21.5)
1.2.0-snapshot.6+1.21.5-fabric30 июн. 2026 г.6Minecraft: 1.21.5fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.21.4)
1.2.0-snapshot.6+1.21.4-fabric30 июн. 2026 г.4Minecraft: 1.21.4fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.21.3)
1.2.0-snapshot.6+1.21.3-fabric30 июн. 2026 г.4Minecraft: 1.21.3fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.21.2)
1.2.0-snapshot.6+1.21.2-fabric30 июн. 2026 г.4Minecraft: 1.21.2fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.21.11)
1.2.0-snapshot.6+1.21.11-fabric30 июн. 2026 г.19Minecraft: 1.21.11fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.21.10)
1.2.0-snapshot.6+1.21.10-fabric30 июн. 2026 г.5Minecraft: 1.21.10fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.21.1)
1.2.0-snapshot.6+1.21.1-fabric30 июн. 2026 г.5Minecraft: 1.21.1fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.20)
1.2.0-snapshot.6+1.20-fabric30 июн. 2026 г.2Minecraft: 1.20fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.20.6)
1.2.0-snapshot.6+1.20.6-fabric30 июн. 2026 г.3Minecraft: 1.20.6fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.20.5)
1.2.0-snapshot.6+1.20.5-fabric30 июн. 2026 г.3Minecraft: 1.20.5fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.20.4)
1.2.0-snapshot.6+1.20.4-fabric30 июн. 2026 г.5Minecraft: 1.20.4fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.20.3)
1.2.0-snapshot.6+1.20.3-fabric30 июн. 2026 г.5Minecraft: 1.20.3fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.20.2)
1.2.0-snapshot.6+1.20.2-fabric30 июн. 2026 г.4Minecraft: 1.20.2fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.6 (Fabric 1.20.1)
1.2.0-snapshot.6+1.20.1-fabric30 июн. 2026 г.4Minecraft: 1.20.1fabric
Что нового
### Added
- **1.12.2 (pre-1.13) Forge class-move baseline (#103/#108/#113).** Old 1.12.2 Forge mods reference Minecraft classes the 1.13 "Flattening" + 1.17 repackaging moved/renamed (`net.minecraft.util.math.BlockPos` -> `core.BlockPos`, `world.World` -> `world.level.Level`, `util.ResourceLocation` -> `resources.Identifier`, the block/item/entity packages, NBT, containers, …). A bundled, jar-validated 1.12.2 -> 26.1 class-move table (117 entries in `forge-1.12.2-class-moves.tsv`, harvested by `scripts/harvest-1.12.2-class-moves.py`) is loaded by the 1.12.2 shim, gated to a 26.1 host. **These are transform-level building blocks, unit-tested at the bytecode level, not yet in-game loading.** A 1.12.2 mod ships only `mcmod.info` (the `mods.toml` format postdates 1.13), so running one end-to-end on modern Forge/NeoForge additionally needs: `mcmod.info` -> `mods.toml` metadata generation (so the loader scans the mod at all), the 1.12.2 SRG member namespace (`func_`/`field_` -> Mojang), and the old `@EventHandler`/`FMLPreInitializationEvent` registry idiom. None of those are built yet, so 1.12.2 mods do not load in-game in this build. Class-moves are the dominant *bytecode* gap (308 of 344 issues on a real 1.12.2 mod). Transform-tested (`Forge1122ClassMovesTest`).
- **1.12.2 Block/Item Properties-constructor bridge (#80).** After the class moves, a 1.12.2 custom block/item still wouldn't *construct*: 26.1 made `Block` and `Item` Properties-constructed, but a 1.12.2 block calls `super(Material)` and a 1.12.2 item calls `super()`, neither of which has a matching ctor (and `Material` itself was removed). Two new transformer mechanisms bridge this: a super-constructor *replace* (pop the old args, push a fresh default `Properties`) turns `super(Material)` into `super(BlockBehaviour.Properties.of())`, and the existing insert-defaults path now constructs a real default for `Properties` types (not `null`, which would NPE) so `super()` becomes `super(new Item.Properties())`. A static-field *nuller* neutralizes reads of removed-class constants (`Material.IRON`) so the field read doesn't fault before the super call. Default properties only (material-specific settings are lost). At the transform level the block/item now constructs; like the class-move table this is a bytecode building block gated behind the same in-game prerequisites noted above (metadata generation, SRG members, registry idiom). Gated to a 26.1 host. Transform-tested (`Forge1122ClassMovesTest`).
### Fixed
- **The Betweenlands: missing `ISound` sound class (#113).** The 1.12.2 client sound interface `net.minecraft.client.audio.ISound` was relocated by the 1.17 repackaging to `net.minecraft.client.resources.sounds.SoundInstance` (same name on 1.20.1 and 26.1), and Retromod didn't map it. Added the class move (ungated; the target exists on every supported host). It was The Betweenlands' only transform gap, so the mod should now transform cleanly.
- **Chunk force-loading crash on 26.1 (Chunky and other 1.18-era chunk mods).** 26.1 renamed `ServerChunkCache.addRegionTicket(TicketType, ChunkPos, int, T)` to `addTicketWithRadius(TicketType, ChunkPos, int)` and **dropped the value argument**, so an old chunk-loading mod's 4-arg call had no target on 26.1 and crashed worlds on load. A plain rename can't fix it (the arity changed), so Retromod gained a generic **arg-dropping method redirect** (it pops the dropped trailing value, then calls the shorter renamed method) and registered the chunk-ticket case. Verified on the real Chunky jar: `FabricWorld` now calls the 3-arg `addTicketWithRadius`. Mechanism tested by `ArgDropRedirectTest`. (Chunky also mixins `ThreadedAnvilChunkStorage`, so full Chunky compatibility needs that checked in-game; this removes the `addRegionTicket` crash.)
release
Retromod 1.2.0-snapshot.5
1.2.0-snapshot.529 июн. 2026 г.3Minecraft: 1.21.11neoforge
release
Retromod 1.2.0-snapshot.5
1.2.0-snapshot.529 июн. 2026 г.1Minecraft: 1.21.10neoforge
release
Retromod 1.2.0-snapshot.5
1.2.0-snapshot.529 июн. 2026 г.8Minecraft: 1.21.11forge
release
Retromod 1.2.0-snapshot.5
1.2.0-snapshot.529 июн. 2026 г.2Minecraft: 1.21.10forge
release
Retromod 1.2.0-snapshot.5
1.2.0-snapshot.529 июн. 2026 г.23Minecraft: 1.21.11fabric, quilt
release
Retromod 1.2.0-snapshot.5
1.2.0-snapshot.529 июн. 2026 г.5Minecraft: 1.21.10fabric, quilt
release
Retromod 1.2.0-snapshot.5 (NeoForge 26.2)
1.2.0-snapshot.5+26.2-neoforge29 июн. 2026 г.10Minecraft: 26.2neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 26.1.2)
1.2.0-snapshot.5+26.1.2-neoforge29 июн. 2026 г.7Minecraft: 26.1.2neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.21)
1.2.0-snapshot.5+1.21-neoforge29 июн. 2026 г.2Minecraft: 1.21neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.21.9)
1.2.0-snapshot.5+1.21.9-neoforge29 июн. 2026 г.1Minecraft: 1.21.9neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.21.8)
1.2.0-snapshot.5+1.21.8-neoforge29 июн. 2026 г.2Minecraft: 1.21.8neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.21.7)
1.2.0-snapshot.5+1.21.7-neoforge29 июн. 2026 г.2Minecraft: 1.21.7neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.21.6)
1.2.0-snapshot.5+1.21.6-neoforge29 июн. 2026 г.1Minecraft: 1.21.6neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.21.5)
1.2.0-snapshot.5+1.21.5-neoforge29 июн. 2026 г.1Minecraft: 1.21.5neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.21.4)
1.2.0-snapshot.5+1.21.4-neoforge29 июн. 2026 г.1Minecraft: 1.21.4neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.21.3)
1.2.0-snapshot.5+1.21.3-neoforge29 июн. 2026 г.1Minecraft: 1.21.3neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.21.2)
1.2.0-snapshot.5+1.21.2-neoforge29 июн. 2026 г.3Minecraft: 1.21.2neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.21.1)
1.2.0-snapshot.5+1.21.1-neoforge29 июн. 2026 г.40Minecraft: 1.21.1neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.20.6)
1.2.0-snapshot.5+1.20.6-neoforge29 июн. 2026 г.3Minecraft: 1.20.6neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.20.5)
1.2.0-snapshot.5+1.20.5-neoforge29 июн. 2026 г.9Minecraft: 1.20.5neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.20.4)
1.2.0-snapshot.5+1.20.4-neoforge29 июн. 2026 г.2Minecraft: 1.20.4neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.20.3)
1.2.0-snapshot.5+1.20.3-neoforge29 июн. 2026 г.2Minecraft: 1.20.3neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.20.2)
1.2.0-snapshot.5+1.20.2-neoforge29 июн. 2026 г.1Minecraft: 1.20.2neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (NeoForge 1.20.1)
1.2.0-snapshot.5+1.20.1-neoforge29 июн. 2026 г.6Minecraft: 1.20.1neoforge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 26.2)
1.2.0-snapshot.5+26.2-forge29 июн. 2026 г.12Minecraft: 26.2forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 26.1)
1.2.0-snapshot.5+26.1-forge29 июн. 2026 г.5Minecraft: 26.1forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 26.1.2)
1.2.0-snapshot.5+26.1.2-forge29 июн. 2026 г.4Minecraft: 26.1.2forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 26.1.1)
1.2.0-snapshot.5+26.1.1-forge29 июн. 2026 г.1Minecraft: 26.1.1forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.21)
1.2.0-snapshot.5+1.21-forge29 июн. 2026 г.2Minecraft: 1.21forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.21.9)
1.2.0-snapshot.5+1.21.9-forge29 июн. 2026 г.3Minecraft: 1.21.9forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.21.8)
1.2.0-snapshot.5+1.21.8-forge29 июн. 2026 г.1Minecraft: 1.21.8forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.21.7)
1.2.0-snapshot.5+1.21.7-forge29 июн. 2026 г.2Minecraft: 1.21.7forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.21.6)
1.2.0-snapshot.5+1.21.6-forge29 июн. 2026 г.1Minecraft: 1.21.6forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.21.5)
1.2.0-snapshot.5+1.21.5-forge29 июн. 2026 г.1Minecraft: 1.21.5forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.21.4)
1.2.0-snapshot.5+1.21.4-forge29 июн. 2026 г.1Minecraft: 1.21.4forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.21.3)
1.2.0-snapshot.5+1.21.3-forge29 июн. 2026 г.2Minecraft: 1.21.3forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.21.2)
1.2.0-snapshot.5+1.21.2-forge29 июн. 2026 г.1Minecraft: 1.21.2forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.21.11)
1.2.0-snapshot.5+1.21.11-forge29 июн. 2026 г.3Minecraft: 1.21.11forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.21.10)
1.2.0-snapshot.5+1.21.10-forge29 июн. 2026 г.3Minecraft: 1.21.10forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.21.1)
1.2.0-snapshot.5+1.21.1-forge29 июн. 2026 г.9Minecraft: 1.21.1forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.20)
1.2.0-snapshot.5+1.20-forge29 июн. 2026 г.3Minecraft: 1.20forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.20.6)
1.2.0-snapshot.5+1.20.6-forge29 июн. 2026 г.1Minecraft: 1.20.6forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.20.5)
1.2.0-snapshot.5+1.20.5-forge29 июн. 2026 г.3Minecraft: 1.20.5forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.20.4)
1.2.0-snapshot.5+1.20.4-forge29 июн. 2026 г.2Minecraft: 1.20.4forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.20.3)
1.2.0-snapshot.5+1.20.3-forge29 июн. 2026 г.2Minecraft: 1.20.3forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.20.2)
1.2.0-snapshot.5+1.20.2-forge29 июн. 2026 г.5Minecraft: 1.20.2forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Forge 1.20.1)
1.2.0-snapshot.5+1.20.1-forge29 июн. 2026 г.30Minecraft: 1.20.1forge
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 26.2)
1.2.0-snapshot.5+26.2-fabric29 июн. 2026 г.40Minecraft: 26.2fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 26.1)
1.2.0-snapshot.5+26.1-fabric29 июн. 2026 г.3Minecraft: 26.1fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 26.1.2)
1.2.0-snapshot.5+26.1.2-fabric29 июн. 2026 г.24Minecraft: 26.1.2fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 26.1.1)
1.2.0-snapshot.5+26.1.1-fabric29 июн. 2026 г.1Minecraft: 26.1.1fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.21)
1.2.0-snapshot.5+1.21-fabric29 июн. 2026 г.1Minecraft: 1.21fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.21.9)
1.2.0-snapshot.5+1.21.9-fabric29 июн. 2026 г.2Minecraft: 1.21.9fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.21.8)
1.2.0-snapshot.5+1.21.8-fabric29 июн. 2026 г.2Minecraft: 1.21.8fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.21.7)
1.2.0-snapshot.5+1.21.7-fabric29 июн. 2026 г.2Minecraft: 1.21.7fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.21.6)
1.2.0-snapshot.5+1.21.6-fabric29 июн. 2026 г.3Minecraft: 1.21.6fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.21.5)
1.2.0-snapshot.5+1.21.5-fabric29 июн. 2026 г.2Minecraft: 1.21.5fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.21.4)
1.2.0-snapshot.5+1.21.4-fabric29 июн. 2026 г.4Minecraft: 1.21.4fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.21.3)
1.2.0-snapshot.5+1.21.3-fabric29 июн. 2026 г.1Minecraft: 1.21.3fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.21.2)
1.2.0-snapshot.5+1.21.2-fabric29 июн. 2026 г.2Minecraft: 1.21.2fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.21.11)
1.2.0-snapshot.5+1.21.11-fabric29 июн. 2026 г.1Minecraft: 1.21.11fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.21.10)
1.2.0-snapshot.5+1.21.10-fabric29 июн. 2026 г.1Minecraft: 1.21.10fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.21.1)
1.2.0-snapshot.5+1.21.1-fabric29 июн. 2026 г.6Minecraft: 1.21.1fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.20)
1.2.0-snapshot.5+1.20-fabric29 июн. 2026 г.3Minecraft: 1.20fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.20.6)
1.2.0-snapshot.5+1.20.6-fabric29 июн. 2026 г.3Minecraft: 1.20.6fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.20.5)
1.2.0-snapshot.5+1.20.5-fabric29 июн. 2026 г.3Minecraft: 1.20.5fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.20.4)
1.2.0-snapshot.5+1.20.4-fabric29 июн. 2026 г.4Minecraft: 1.20.4fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.20.3)
1.2.0-snapshot.5+1.20.3-fabric29 июн. 2026 г.3Minecraft: 1.20.3fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.20.2)
1.2.0-snapshot.5+1.20.2-fabric29 июн. 2026 г.2Minecraft: 1.20.2fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.5 (Fabric 1.20.1)
1.2.0-snapshot.5+1.20.1-fabric29 июн. 2026 г.6Minecraft: 1.20.1fabric
Что нового
### Fixed
- **Retromod didn't initialize at all on Forge (#102).** Retromod's main jar registered a Forge `IModLocator` service (the #78 `mods/Retromod/` locator). Forge's `ModDirTransformerDiscoverer` claims any `mods/` jar that declares that service onto the early service/transformer layer, where it loads before mod discovery and is never scanned as a `@Mod`. So Retromod loaded as a bare locator, its `RetromodForge` `@Mod` entry never ran, and it transformed nothing; an old mod (Cart's, 1.16.5) was then rejected by Forge's own version check and looked like Retromod "ignored" it. The service registration is removed, so Retromod loads as a normal Forge mod again. This regressed in snapshot.2 when the Forge locator landed, and the snapshot.2 in-game check only verified the locator loading `mods/Retromod/`, not Retromod's own `@Mod`, so it was masked. The locator class is kept for a future locator-only stub jar; until then `mods/Retromod/` loading is NeoForge-only and Forge users place jars directly in `mods/`. Regression-tested (`RetromodForgeModLocatorTest` asserts the service stays unregistered).
- **Transform aborted when a mod referenced a class in a protected Mixin package (#102).** The transform verifier (a diagnostic that checks a transformed mod's references against the target MC) loads referenced classes to probe them. When one is a `@Mixin` class in a package owned by a `*.mixins.json`, Mixin throws `IllegalClassLoadError` on the direct load, an `Error` rather than an `Exception`. The probes caught only `Exception`, so the Error escaped and killed the entire transform pass, taking every input mod down with it (seen with the Mine Mine No Mi addon Cart's, which references a `mineminenomi` mixin class). The three resolution probes and the verifier's top-level guard now catch `Throwable`, so this diagnostic can never abort a transform. Regression-tested (`TransformVerifierTest` locks the probes' no-throw contract).
- **CLI/AOT emitted broken `ResourceLocation` constructor bytecode (CLI did not match an in-game boot).** The offline `transform`/`batch`/`aot` paths applied the class-move table but not the `ResourceLocation(String)` -> `Identifier.parse` / `(String, String)` -> `Identifier.fromNamespaceAndPath` constructor redirects that the Fabric/NeoForge/Forge runtime applies, so any mod that constructs a `ResourceLocation` got a raw `new Identifier(...)` call (a constructor 26.1 removed) and would fail to load when prepared via the CLI/AOT, even though the same mod transformed in-game worked. AOT is the recommended NeoForge flow, so this hit the recommended path. Fixed by routing all five transform paths (three runtime entry points + CLI + AOT) through one shared `registerIdentifierCtorRedirects` helper, removing the "keep in sync by hand" drift. Verified byte-for-byte: 26 classes across a 6-mod corpus that previously emitted `invokespecial Identifier.<init>` now emit `invokestatic Identifier.parse` / `fromNamespaceAndPath`, matching the runtime. Regression-tested (`RetromodCliAuxRedirectsTest`).
### Changed
- Internal code cleanup and hot-path optimizations, no behavior change. Tightened comments and structure across the source, and applied behavior-preserving performance fixes to the transform core: an O(1) constructor-redirect fast path replacing a per-`NEW`-opcode key scan, constant sets and regexes hoisted out of per-class loops, single map lookups in the reflection remapper instead of contains-then-get, and a skipped guaranteed-no-op pass in the intermediary remapper. Verified byte-identical transform output across a 6-mod corpus (785 classes, MC 1.16.5-1.20.4 to 26.1) on top of the full test suite.
- Resource and allocation hardening from a leak audit, no behavior change. Wrapped unclosed `Files.walk`/`Files.list` streams and `jar.getInputStream()` reads in try-with-resources across the AOT, hybrid, quilt, legacy, datapack, and pre-launch paths (file-descriptor leaks), shut down an executor in a `finally`, deleted temp dirs in a `finally`, and replaced a per-class-load full-`String` copy in the JIT agent's marker scan with a direct byte search. Verified byte-identical transform output and the full test suite.
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.26Minecraft: 26.2neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.14Minecraft: 26.1.2neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.9Minecraft: 1.21.11neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.6Minecraft: 1.21.10neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.21.9neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.4Minecraft: 1.21.8neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.1Minecraft: 1.21.7neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.9Minecraft: 1.21.6neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.0Minecraft: 1.21.5neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.9Minecraft: 1.21.4neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.21.3neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.1Minecraft: 1.21.2neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.58Minecraft: 1.21.1neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.21neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.20.6neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.20.5neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.20.4neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.1Minecraft: 1.20.3neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.20.2neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.4Minecraft: 1.20.1neoforge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.19Minecraft: 26.2forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.6Minecraft: 26.1.2forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.3Minecraft: 26.1.1forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.8Minecraft: 26.1forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.8Minecraft: 1.21.11forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.5Minecraft: 1.21.10forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.3Minecraft: 1.21.9forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.21.8forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.21.7forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.21.6forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.21.5forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.1Minecraft: 1.21.4forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.21.3forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.21.2forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.9Minecraft: 1.21.1forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.1Minecraft: 1.21forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.6Minecraft: 1.20.6forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.20.5forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.3Minecraft: 1.20.4forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.20.3forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.20.2forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.61Minecraft: 1.20.1forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.20forge
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.179Minecraft: 26.2fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.53Minecraft: 26.1.2fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.6Minecraft: 26.1.1fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.8Minecraft: 26.1fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.37Minecraft: 1.21.11fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.1Minecraft: 1.21.10fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.5Minecraft: 1.21.9fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.7Minecraft: 1.21.8fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.5Minecraft: 1.21.7fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.21.6fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.15Minecraft: 1.21.5fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.9Minecraft: 1.21.4fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.2Minecraft: 1.21.3fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.3Minecraft: 1.21.2fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.23Minecraft: 1.21.1fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.4Minecraft: 1.21fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.4Minecraft: 1.20.6fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.3Minecraft: 1.20.5fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.5Minecraft: 1.20.4fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.7Minecraft: 1.20.3fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.3Minecraft: 1.20.2fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.27Minecraft: 1.20.1fabric, quilt
release
Retromod 1.2.0-snapshot.4
1.2.0-snapshot.425 июн. 2026 г.11Minecraft: 1.20fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.15Minecraft: 26.2neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.12Minecraft: 26.1.2neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.1Minecraft: 1.21.11neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.6Minecraft: 1.21.10neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.21.9neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.21.8neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.21.7neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.21.6neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.21.5neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.21.4neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.21.3neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.5Minecraft: 1.21.2neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.83Minecraft: 1.21.1neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.21neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.4Minecraft: 1.20.6neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.20.5neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.7Minecraft: 1.20.4neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.20.3neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.4Minecraft: 1.20.2neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.5Minecraft: 1.20.1neoforge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.21Minecraft: 26.2forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.5Minecraft: 26.1.2forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 26.1.1forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 26.1forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.21.11forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.21.10forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.21.9forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.21.8forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.1Minecraft: 1.21.7forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.21.6forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.1Minecraft: 1.21.5forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.21.4forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.1Minecraft: 1.21.3forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.21.2forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.8Minecraft: 1.21.1forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.21forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.20.6forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.1Minecraft: 1.20.5forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.1Minecraft: 1.20.4forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.20.3forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.20.2forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.57Minecraft: 1.20.1forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.4Minecraft: 1.20forge
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.66Minecraft: 26.2fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.40Minecraft: 26.1.2fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 26.1.1fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.4Minecraft: 26.1fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.24Minecraft: 1.21.11fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.21.10fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.21.9fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.4Minecraft: 1.21.8fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.1Minecraft: 1.21.7fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.21.6fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.4Minecraft: 1.21.5fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.21.4fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.4Minecraft: 1.21.3fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.21.2fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.14Minecraft: 1.21.1fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.21fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.20.6fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.20.5fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.2Minecraft: 1.20.4fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.20.3fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.20.2fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.8Minecraft: 1.20.1fabric, quilt
release
Retromod 1.2.0-snapshot.3
1.2.0-snapshot.323 июн. 2026 г.3Minecraft: 1.20fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.26Minecraft: 26.2neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.11Minecraft: 26.1.2neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.5Minecraft: 1.21.11neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.21.10neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.21.9neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.21.8neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.21.7neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.21.6neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.21.5neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.21.4neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.3Minecraft: 1.21.3neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.3Minecraft: 1.21.2neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.34Minecraft: 1.21.1neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.21neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.20.6neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.20.5neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.20.4neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.3Minecraft: 1.20.3neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.3Minecraft: 1.20.2neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.20.1neoforge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.5Minecraft: 26.2forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.10Minecraft: 26.1.2forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 26.1.1forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 26.1forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.21.11forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.21.10forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.21.9forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.21.8forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.21.7forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.21.6forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.21.5forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.21.4forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.21.3forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.21.2forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.21.1forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.21forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.20.6forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.4Minecraft: 1.20.5forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.1Minecraft: 1.20.4forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.20.3forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.20.2forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.36Minecraft: 1.20.1forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.20forge
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.75Minecraft: 26.2fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.58Minecraft: 26.1.2fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.6Minecraft: 26.1.1fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.4Minecraft: 26.1fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.19Minecraft: 1.21.11fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.21.10fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.3Minecraft: 1.21.9fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.3Minecraft: 1.21.8fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.21.7fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.21.6fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.21.5fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.6Minecraft: 1.21.4fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.3Minecraft: 1.21.3fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.4Minecraft: 1.21.2fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.15Minecraft: 1.21.1fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.3Minecraft: 1.21fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.3Minecraft: 1.20.6fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.20.5fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.3Minecraft: 1.20.4fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.3Minecraft: 1.20.3fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.2Minecraft: 1.20.2fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.5Minecraft: 1.20.1fabric, quilt
release
Retromod 1.2.0-snapshot.2
1.2.0-snapshot.221 июн. 2026 г.3Minecraft: 1.20fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.34Minecraft: 26.2neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.24Minecraft: 26.1.2neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.3Minecraft: 1.21.11neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21.10neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.1Minecraft: 1.21.9neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21.8neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.3Minecraft: 1.21.7neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21.6neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.3Minecraft: 1.21.5neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.4Minecraft: 1.21.4neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.1Minecraft: 1.21.3neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21.2neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.71Minecraft: 1.21.1neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.3Minecraft: 1.20.6neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.20.5neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.1Minecraft: 1.20.4neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.3Minecraft: 1.20.3neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.20.2neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.5Minecraft: 1.20.1neoforge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.13Minecraft: 26.1.2forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.4Minecraft: 26.1.1forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.4Minecraft: 26.1forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.5Minecraft: 1.21.11forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21.10forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.9Minecraft: 1.21.9forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.4Minecraft: 1.21.8forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21.7forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21.6forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.3Minecraft: 1.21.5forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21.4forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21.3forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.1Minecraft: 1.21.2forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.11Minecraft: 1.21.1forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.1Minecraft: 1.21forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.3Minecraft: 1.20.6forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.3Minecraft: 1.20.5forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.1Minecraft: 1.20.4forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.3Minecraft: 1.20.3forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.20.2forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.51Minecraft: 1.20.1forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.20forge
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.131Minecraft: 26.2fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.69Minecraft: 26.1.2fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.12Minecraft: 26.1.1fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.12Minecraft: 26.1fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.40Minecraft: 1.21.11fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.5Minecraft: 1.21.10fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21.9fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.9Minecraft: 1.21.8fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.3Minecraft: 1.21.7fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21.6fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.3Minecraft: 1.21.5fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.7Minecraft: 1.21.4fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21.3fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21.2fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.22Minecraft: 1.21.1fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.21fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.20.6fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.20.5fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.13Minecraft: 1.20.4fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.20.3fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.3Minecraft: 1.20.2fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.5Minecraft: 1.20.1fabric, quilt
release
Retromod 1.2.0-snapshot.1
1.2.0-snapshot.119 июн. 2026 г.2Minecraft: 1.20fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.18Minecraft: 26.2neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.14Minecraft: 26.1.2neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.5Minecraft: 1.21.11neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.7Minecraft: 1.21.10neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.5Minecraft: 1.21.9neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.3Minecraft: 1.21.8neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 1.21.7neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.4Minecraft: 1.21.6neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.1Minecraft: 1.21.5neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.7Minecraft: 1.21.4neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.4Minecraft: 1.21.3neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 1.21.2neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.69Minecraft: 1.21.1neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.5Minecraft: 1.21neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.1Minecraft: 1.20.6neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.3Minecraft: 1.20.5neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.3Minecraft: 1.20.4neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.1Minecraft: 1.20.3neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.1Minecraft: 1.20.2neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.4Minecraft: 1.20.1neoforge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.5Minecraft: 26.1.2forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.4Minecraft: 26.1.1forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 26.1forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.3Minecraft: 1.21.11forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.1Minecraft: 1.21.10forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 1.21.9forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.1Minecraft: 1.21.8forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.1Minecraft: 1.21.7forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.1Minecraft: 1.21.6forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.3Minecraft: 1.21.5forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.1Minecraft: 1.21.4forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 1.21.3forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.1Minecraft: 1.21.2forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.12Minecraft: 1.21.1forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 1.21forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.1Minecraft: 1.20.6forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 1.20.5forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.3Minecraft: 1.20.4forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 1.20.3forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 1.20.2forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.62Minecraft: 1.20.1forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 1.20forge
release
Retromod 1.1.0
1.1.018 июн. 2026 г.87Minecraft: 26.2fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.41Minecraft: 26.1.2fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.5Minecraft: 26.1.1fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.6Minecraft: 26.1fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.17Minecraft: 1.21.11fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.4Minecraft: 1.21.10fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.6Minecraft: 1.21.9fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 1.21.8fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 1.21.7fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.3Minecraft: 1.21.6fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.3Minecraft: 1.21.5fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.15Minecraft: 1.21.4fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.3Minecraft: 1.21.3fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 1.21.2fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.23Minecraft: 1.21.1fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.3Minecraft: 1.21fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.3Minecraft: 1.20.6fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 1.20.5fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.8Minecraft: 1.20.4fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.2Minecraft: 1.20.3fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.3Minecraft: 1.20.2fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.10Minecraft: 1.20.1fabric, quilt
release
Retromod 1.1.0
1.1.018 июн. 2026 г.3Minecraft: 1.20fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.25Minecraft: 26.2neoforge
Что нового
# Retromod 1.1.0-rc.1
**Self-hash (authenticity):** `DE1FF3775BA35DA17AE4D43B0F3CE1FAA50D9F23E5B7D4E73AA4EBF43355F9DB`
---
**Release candidate for 1.1.0.** Everything from the snapshot line — the full-catalog audit and the fixes it forced, ~20 renamed/removed-event API bridges (verified *firing* in a live world), the Cardinal Components package move, the `Tuple` polyfill, NeoForge redirect corrections, and first-day **MC 26.2** support — plus the #94 fixes and the first round of **MC 26.2 Vulkan compatibility**.
### Added
- **MC 26.2 NeoForge builds.** Now that NeoForge has a 26.2 release, the matrix ships `+26.2` jars for **Fabric and NeoForge**. (Forge has no 26.2 build yet — no Forge 26.2 jar.)
- **Vulkan compatibility — prefer OpenGL for old mods.** 26.2 makes Vulkan the default renderer; old mods doing raw OpenGL render correctly on the still-present OpenGL backend. On a 26.2+ client Retromod sets `graphicsApi:opengl` in `options.txt` — only if you haven't chosen a backend yourself (an explicit Vulkan choice is respected with a warning), opt-out via `-Dretromod.graphics.noPreference=true`. Mods using Minecraft's modern render API are unaffected (they run on Vulkan too).
- **Render-state soft-fail.** The imperative `RenderSystem` state setters (`enableBlend`/`blendFunc`/…) deleted in the modern blaze3d refactor are now neutralized at the call site, so an old mod **loads and runs** instead of crashing with `NoSuchMethodError` (the state is inert — a soft-fail, not a translation).
### Fixed
- #94: **mods that bundle Forge Config API Port crashed on 26.1 with `NoSuchMethodError: PayloadTypeRegistry.configurationClientbound()`** (CoroUtil / Watut, Fabric 26.1.2). fabric-api renamed the static `PayloadTypeRegistry` accessors *twice* — `configurationClientbound()`/`playClientbound()`/… (≤0.111) → `configurationS2C()`/`playS2C()`/… (1.21.4) → `clientboundConfiguration()`/`clientboundPlay()`/… (26.1) — so a jar-in-jar library built against either older fabric-api hits the gone name on a 26.1 host. The 26.1 Fabric shim now maps **both** old generations to the 26.1 names (S2C = server→client = clientbound; C2S = serverbound), verified against fabric-api 0.119.4 and 0.145.4. Forge Config API Port is a dependency of a large number of mods, so this unblocks well beyond the two reported. **Verified in-game: the error is gone.**
- **Retromod was breaking *every* mod that uses Forge Config API Port.** `ForgeConfigApiPortShim` redirected `net.minecraftforge.common.ForgeConfigSpec` (and `$Builder`, `ModLoadingContext`, `ModConfig$Type`) onto `fuzs.forgeconfigapiport.api.config.v2.*` targets that **don't exist** — the Fabric port re-implements the Forge config classes at their *original* `net.minecraftforge.*` names, so a mod's reference already resolves with no redirect needed. The bogus redirect rewrote working references onto absent classes → `NoClassDefFoundError` (caught while verifying #94: CoroUtil crashed at `ModConfigDataFabric.writeConfigFile`). The redirects are removed — the port provides the originals. **Verified in-game: the `NoClassDefFoundError` is gone.**
> **Known issue, tracked for 1.2.0:** verifying #94 surfaced one deeper, *pre-existing* problem in forge-config-api-port — a `VerifyError` in `net/neoforged/fml/config/ConfigTracker.loadConfig` (a `Throwable` arg typed `Object` after transform). It's unrelated to the two fixes above (1.1.0 just got far enough to reveal it) and needs a bytecode decompile-and-diff rather than a redirect, so it's scoped to the 1.2.0 "general update." Some forge-config-api-port mods that now get *past* the two fixes above may still hit this on the config-loading path.
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.12Minecraft: 26.1.2neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.1Minecraft: 1.21.11neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.1Minecraft: 1.21.10neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.1Minecraft: 1.21.9neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.21.8neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.1Minecraft: 1.21.7neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.3Minecraft: 1.21.6neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.1Minecraft: 1.21.5neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.1Minecraft: 1.21.4neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.21.3neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.21.2neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.45Minecraft: 1.21.1neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.3Minecraft: 1.21neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.20.6neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.20.5neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.20.4neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.20.3neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.20.2neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.6Minecraft: 1.20.1neoforge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.6Minecraft: 26.1.2forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.3Minecraft: 26.1.1forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.3Minecraft: 26.1forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.21.11forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.21.10forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.4Minecraft: 1.21.9forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.1Minecraft: 1.21.8forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.21.7forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.1Minecraft: 1.21.6forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.4Minecraft: 1.21.5forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.4Minecraft: 1.21.4forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.21.3forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.1Minecraft: 1.21.2forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.3Minecraft: 1.21.1forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.21forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.3Minecraft: 1.20.6forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.20.5forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.3Minecraft: 1.20.4forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.20.3forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.20.2forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.20Minecraft: 1.20.1forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.1Minecraft: 1.20forge
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.62Minecraft: 26.2fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.40Minecraft: 26.1.2fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.4Minecraft: 26.1.1fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 26.1fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.17Minecraft: 1.21.11fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.4Minecraft: 1.21.10fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.21.9fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.4Minecraft: 1.21.8fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.3Minecraft: 1.21.7fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.4Minecraft: 1.21.6fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.21.5fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.3Minecraft: 1.21.4fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.3Minecraft: 1.21.3fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.5Minecraft: 1.21.2fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.14Minecraft: 1.21.1fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.4Minecraft: 1.21fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.3Minecraft: 1.20.6fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.20.5fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.9Minecraft: 1.20.4fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.20.3fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.3Minecraft: 1.20.2fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.2Minecraft: 1.20.1fabric, quilt
beta
Retromod 1.1.0-rc.1
1.1.0-rc.116 июн. 2026 г.3Minecraft: 1.20fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.32Minecraft: 26.1.2neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.4Minecraft: 1.21.11neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.21.10neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.1Minecraft: 1.21.9neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.21.8neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.1Minecraft: 1.21.7neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.4Minecraft: 1.21.6neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.4Minecraft: 1.21.5neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.4Minecraft: 1.21.4neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.21.3neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.1Minecraft: 1.21.2neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.40Minecraft: 1.21.1neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.1Minecraft: 1.21neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.1Minecraft: 1.20.6neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.1Minecraft: 1.20.5neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 1.20.4neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 1.20.3neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.1Minecraft: 1.20.2neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.5Minecraft: 1.20.1neoforge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.15Minecraft: 26.1.2forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 26.1.1forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.7Minecraft: 26.1forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.6Minecraft: 1.21.11forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.1Minecraft: 1.21.10forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 1.21.9forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 1.21.8forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 1.21.7forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 1.21.6forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 1.21.5forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.1Minecraft: 1.21.4forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 1.21.3forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.4Minecraft: 1.21.2forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.21.1forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.1Minecraft: 1.21forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 1.20.6forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 1.20.5forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.20.4forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.1Minecraft: 1.20.3forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.20.2forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.44Minecraft: 1.20.1forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.20forge
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.13Minecraft: 26.2-rc-1fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.62Minecraft: 26.1.2fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.5Minecraft: 26.1.1fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.6Minecraft: 26.1fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.22Minecraft: 1.21.11fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.21.10fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 1.21.9fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.21.8fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.5Minecraft: 1.21.7fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.21.6fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.21.5fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 1.21.4fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.21.3fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 1.21.2fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.10Minecraft: 1.21.1fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.21fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.2Minecraft: 1.20.6fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.20.5fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.4Minecraft: 1.20.4fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.20.3fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.20.2fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.8Minecraft: 1.20.1fabric, quilt
release
Retromod 1.1.0-snapshot.5
1.1.0-snapshot.515 июн. 2026 г.3Minecraft: 1.20fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.47Minecraft: 26.1.2neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.11Minecraft: 1.21.11neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.3Minecraft: 1.21.10neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 1.21.9neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 1.21.8neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.3Minecraft: 1.21.7neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.4Minecraft: 1.21.6neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.3Minecraft: 1.21.5neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 1.21.4neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.3Minecraft: 1.21.3neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.3Minecraft: 1.21.2neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.63Minecraft: 1.21.1neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.1Minecraft: 1.21neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.3Minecraft: 1.20.6neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.4Minecraft: 1.20.5neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.3Minecraft: 1.20.4neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.5Minecraft: 1.20.3neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 1.20.2neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.6Minecraft: 1.20.1neoforge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.21Minecraft: 26.1.2forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 26.1.1forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.4Minecraft: 26.1forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.6Minecraft: 1.21.11forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.1Minecraft: 1.21.10forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.3Minecraft: 1.21.9forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.3Minecraft: 1.21.8forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.1Minecraft: 1.21.7forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 1.21.6forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 1.21.5forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 1.21.4forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.1Minecraft: 1.21.3forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.1Minecraft: 1.21.2forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.6Minecraft: 1.21.1forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 1.21forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.1Minecraft: 1.20.6forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.1Minecraft: 1.20.5forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 1.20.4forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.1Minecraft: 1.20.3forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 1.20.2forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.56Minecraft: 1.20.1forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.1Minecraft: 1.20forge
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.20Minecraft: 26.2-rc-1fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.217Minecraft: 26.1.2fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.3Minecraft: 26.1.1fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.13Minecraft: 26.1fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.30Minecraft: 1.21.11fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.1Minecraft: 1.21.10fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 1.21.9fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.10Minecraft: 1.21.8fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 1.21.7fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 1.21.6fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.10Minecraft: 1.21.5fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.4Minecraft: 1.21.4fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.4Minecraft: 1.21.3fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.6Minecraft: 1.21.2fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.19Minecraft: 1.21.1fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.4Minecraft: 1.21fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.8Minecraft: 1.20.6fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.2Minecraft: 1.20.5fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.3Minecraft: 1.20.4fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.3Minecraft: 1.20.3fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.3Minecraft: 1.20.2fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.17Minecraft: 1.20.1fabric, quilt
release
Retromod 1.1.0-snapshot.4
1.1.0-snapshot.412 июн. 2026 г.4Minecraft: 1.20fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.13Minecraft: 26.1.2neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.21.11neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.1Minecraft: 1.21.10neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.21.9neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.21.8neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.21.7neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.21.6neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.21.5neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.1Minecraft: 1.21.4neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.21.3neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.21.2neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.25Minecraft: 1.21.1neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.4Minecraft: 1.21neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.20.6neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.1Minecraft: 1.20.5neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.20.4neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.14Minecraft: 1.20.3neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.20.2neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.20.1neoforge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.9Minecraft: 26.1.2forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 26.1.1forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 26.1forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.4Minecraft: 1.21.11forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.21.10forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.21.9forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.1Minecraft: 1.21.8forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.4Minecraft: 1.21.7forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.1Minecraft: 1.21.6forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.21.5forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.21.4forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.21.3forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.1Minecraft: 1.21.2forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.4Minecraft: 1.21.1forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.21forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.1Minecraft: 1.20.6forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.20.5forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.20.4forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.20.3forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.20.2forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.28Minecraft: 1.20.1forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.20forge
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.40Minecraft: 26.1.2fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 26.1.1fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.4Minecraft: 26.1fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.18Minecraft: 1.21.11fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.21.10fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.21.9fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.10Minecraft: 1.21.8fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.21.7fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.21.6fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.21.5fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.4Minecraft: 1.21.4fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.21.3fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.5Minecraft: 1.21.2fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.8Minecraft: 1.21.1fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.2Minecraft: 1.21fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.4Minecraft: 1.20.6fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.20.5fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.4Minecraft: 1.20.4fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.20.3fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.20.2fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.4Minecraft: 1.20.1fabric, quilt
release
Retromod 1.1.0-snapshot.3
1.1.0-snapshot.310 июн. 2026 г.3Minecraft: 1.20fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.91Minecraft: 26.1.2neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.30Minecraft: 1.21.11neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.5Minecraft: 1.21.10neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.5Minecraft: 1.21.9neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.3Minecraft: 1.21.8neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.3Minecraft: 1.21.7neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.6Minecraft: 1.21.6neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.21.5neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.21.4neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.6Minecraft: 1.21.3neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.21.2neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.180Minecraft: 1.21.1neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.21neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.20.6neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.5Minecraft: 1.20.5neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.3Minecraft: 1.20.4neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.7Minecraft: 1.20.3neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.20.2neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.9Minecraft: 1.20.1neoforge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.53Minecraft: 26.1.2forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 26.1.1forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 26.1forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.12Minecraft: 1.21.11forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.21.10forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.21.9forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.2Minecraft: 1.21.8forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.21.7forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.2Minecraft: 1.21.6forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.3Minecraft: 1.21.5forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.2Minecraft: 1.21.4forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.3Minecraft: 1.21.3forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.2Minecraft: 1.21.2forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.14Minecraft: 1.21.1forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.2Minecraft: 1.21forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.20.6forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.5Minecraft: 1.20.5forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.2Minecraft: 1.20.4forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.20.3forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.5Minecraft: 1.20.2forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.156Minecraft: 1.20.1forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.7Minecraft: 1.20forge
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.270Minecraft: 26.1.2fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.6Minecraft: 26.1.1fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.18Minecraft: 26.1fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.95Minecraft: 1.21.11fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.9Minecraft: 1.21.10fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.3Minecraft: 1.21.9fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.21Minecraft: 1.21.8fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.3Minecraft: 1.21.7fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.21.6fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.9Minecraft: 1.21.5fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.6Minecraft: 1.21.4fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.6Minecraft: 1.21.3fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.5Minecraft: 1.21.2fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.46Minecraft: 1.21.1fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.21fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.7Minecraft: 1.20.6fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.20.5fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.9Minecraft: 1.20.4fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.4Minecraft: 1.20.3fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.5Minecraft: 1.20.2fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.18Minecraft: 1.20.1fabric, quilt
release
Retromod 1.1.0-snapshot.2
1.1.0-snapshot.231 мая 2026 г.3Minecraft: 1.20fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.29Minecraft: 26.1.2neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.7Minecraft: 1.21.11neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.5Minecraft: 1.21.10neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.1Minecraft: 1.21.9neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.5Minecraft: 1.21.8neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.21.7neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.21.6neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.21.5neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.21.4neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.21.3neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.4Minecraft: 1.21.2neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.65Minecraft: 1.21.1neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.5Minecraft: 1.21neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.4Minecraft: 1.20.6neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.20.5neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.20.4neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.2Minecraft: 1.20.3neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.2Minecraft: 1.20.2neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.4Minecraft: 1.20.1neoforge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.23Minecraft: 26.1.2forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 26.1.1forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.6Minecraft: 26.1forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.4Minecraft: 1.21.11forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.2Minecraft: 1.21.10forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.2Minecraft: 1.21.9forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.1Minecraft: 1.21.8forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.1Minecraft: 1.21.7forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.2Minecraft: 1.21.6forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.21.5forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.5Minecraft: 1.21.4forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.1Minecraft: 1.21.3forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.2Minecraft: 1.21.2forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.12Minecraft: 1.21.1forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.1Minecraft: 1.21forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.2Minecraft: 1.20.6forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.2Minecraft: 1.20.5forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.20.4forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.1Minecraft: 1.20.3forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.20.2forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.67Minecraft: 1.20.1forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.20forge
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.64Minecraft: 26.1.2fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.8Minecraft: 26.1.1fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.5Minecraft: 26.1fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.45Minecraft: 1.21.11fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.21.10fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.4Minecraft: 1.21.9fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.5Minecraft: 1.21.8fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.4Minecraft: 1.21.7fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.2Minecraft: 1.21.6fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.2Minecraft: 1.21.5fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.6Minecraft: 1.21.4fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.21.3fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.4Minecraft: 1.21.2fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.17Minecraft: 1.21.1fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.2Minecraft: 1.21fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.5Minecraft: 1.20.6fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.5Minecraft: 1.20.5fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.20.4fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.20.3fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.20.2fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.6Minecraft: 1.20.1fabric, quilt
release
Retromod 1.1.0-snapshot.1
1.1.0-snapshot.129 мая 2026 г.3Minecraft: 1.20fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.62Minecraft: 26.1.2neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.967Minecraft: 1.21.11neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.2Minecraft: 1.21.10neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.2Minecraft: 1.21.9neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.5Minecraft: 1.21.8neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.4Minecraft: 1.21.7neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.21.6neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.21.5neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.2Minecraft: 1.21.4neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.4Minecraft: 1.21.3neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.21.2neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.135Minecraft: 1.21.1neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.1Minecraft: 1.21neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.1Minecraft: 1.20.6neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.2Minecraft: 1.20.5neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.20.4neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.20.3neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.1Minecraft: 1.20.2neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.4Minecraft: 1.20.1neoforge
release
Retromod 1.0.1
1.0.126 мая 2026 г.59Minecraft: 26.1.2forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.4Minecraft: 26.1.1forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.10Minecraft: 26.1forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.15Minecraft: 1.21.11forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.2Minecraft: 1.21.10forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.21.9forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.21.8forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.1Minecraft: 1.21.7forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.21.6forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.2Minecraft: 1.21.5forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.2Minecraft: 1.21.4forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.2Minecraft: 1.21.3forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.21.2forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.29Minecraft: 1.21.1forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.1Minecraft: 1.21forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.20.6forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.1Minecraft: 1.20.5forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.1Minecraft: 1.20.4forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.2Minecraft: 1.20.3forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.20.2forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.92Minecraft: 1.20.1forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.5Minecraft: 1.20forge
release
Retromod 1.0.1
1.0.126 мая 2026 г.106Minecraft: 26.1.2fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.9Minecraft: 26.1.1fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.12Minecraft: 26.1fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.41Minecraft: 1.21.11fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.21.10fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.21.9fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.7Minecraft: 1.21.8fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.2Minecraft: 1.21.7fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.2Minecraft: 1.21.6fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.4Minecraft: 1.21.5fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.7Minecraft: 1.21.4fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.2Minecraft: 1.21.3fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.21.2fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.32Minecraft: 1.21.1fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.6Minecraft: 1.21fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.4Minecraft: 1.20.6fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.4Minecraft: 1.20.5fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.3Minecraft: 1.20.4fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.4Minecraft: 1.20.3fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.2Minecraft: 1.20.2fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.17Minecraft: 1.20.1fabric, quilt
release
Retromod 1.0.1
1.0.126 мая 2026 г.7Minecraft: 1.20fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.21Minecraft: 26.1.2neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.4Minecraft: 1.21.11neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.7Minecraft: 1.21.10neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.21.9neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.21.8neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.21.7neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.1Minecraft: 1.21.6neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.21.5neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.5Minecraft: 1.21.4neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.21.3neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.21.2neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.34Minecraft: 1.21.1neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.21neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.20.6neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.20.5neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.1Minecraft: 1.20.4neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.20.3neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.20.2neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.20.1neoforge
release
Retromod 1.0.0
1.0.026 мая 2026 г.12Minecraft: 26.1.2forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.1Minecraft: 26.1.1forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 26.1forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.21.11forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.1Minecraft: 1.21.10forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.21.9forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.1Minecraft: 1.21.8forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.21.7forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.1Minecraft: 1.21.6forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.21.5forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.21.4forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.21.3forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.21.2forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.5Minecraft: 1.21.1forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.1Minecraft: 1.21forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.1Minecraft: 1.20.6forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.20.5forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.20.4forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.1Minecraft: 1.20.3forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.20.2forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.33Minecraft: 1.20.1forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.20forge
release
Retromod 1.0.0
1.0.026 мая 2026 г.41Minecraft: 26.1.2fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 26.1.1fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.8Minecraft: 26.1fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.6Minecraft: 1.21.11fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.1Minecraft: 1.21.10fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.21.9fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.21.8fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.1Minecraft: 1.21.7fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.21.6fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.1Minecraft: 1.21.5fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.21.4fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.4Minecraft: 1.21.3fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.4Minecraft: 1.21.2fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.4Minecraft: 1.21.1fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.21fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.20.6fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.20.5fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.20.4fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.20.3fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.2Minecraft: 1.20.2fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.9Minecraft: 1.20.1fabric, quilt
release
Retromod 1.0.0
1.0.026 мая 2026 г.3Minecraft: 1.20fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.23Minecraft: 26.1.2neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.21.11neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.4Minecraft: 1.21.10neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.21.9neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.2Minecraft: 1.21.8neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.21.7neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.2Minecraft: 1.21.6neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.4Minecraft: 1.21.5neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.21.4neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.5Minecraft: 1.21.3neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.21.2neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.60Minecraft: 1.21.1neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.1Minecraft: 1.21neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.4Minecraft: 1.20.6neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.2Minecraft: 1.20.5neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.4Minecraft: 1.20.4neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.4Minecraft: 1.20.3neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.4Minecraft: 1.20.2neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.4Minecraft: 1.20.1neoforge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.11Minecraft: 26.1.2forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 26.1.1forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.5Minecraft: 26.1forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.21.11forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.21.10forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.21.9forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.21.8forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.2Minecraft: 1.21.7forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.2Minecraft: 1.21.6forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.1Minecraft: 1.21.5forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.1Minecraft: 1.21.4forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.2Minecraft: 1.21.3forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.21.2forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.6Minecraft: 1.21.1forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.4Minecraft: 1.21forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.4Minecraft: 1.20.6forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.4Minecraft: 1.20.5forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.20.4forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.4Minecraft: 1.20.3forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.5Minecraft: 1.20.2forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.32Minecraft: 1.20.1forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.20forge
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.53Minecraft: 26.1.2fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.5Minecraft: 26.1.1fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.6Minecraft: 26.1fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.13Minecraft: 1.21.11fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.5Minecraft: 1.21.10fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.5Minecraft: 1.21.9fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.5Minecraft: 1.21.8fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.21.7fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.6Minecraft: 1.21.6fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.21.5fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.6Minecraft: 1.21.4fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.7Minecraft: 1.21.3fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.4Minecraft: 1.21.2fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.14Minecraft: 1.21.1fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.21fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.2Minecraft: 1.20.6fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.2Minecraft: 1.20.5fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.3Minecraft: 1.20.4fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.4Minecraft: 1.20.3fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.2Minecraft: 1.20.2fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.6Minecraft: 1.20.1fabric, quilt
beta
Retromod 1.0.0-rc.1
1.0.0-rc.124 мая 2026 г.5Minecraft: 1.20fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.26Minecraft: 26.1.2neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.5Minecraft: 1.21.11neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.2Minecraft: 1.21.10neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 1.21.9neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 1.21.8neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.2Minecraft: 1.21.7neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.6Minecraft: 1.21.6neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.1Minecraft: 1.21.5neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.2Minecraft: 1.21.4neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.4Minecraft: 1.21.3neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.2Minecraft: 1.21.2neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.34Minecraft: 1.21.1neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 1.21neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 1.20.6neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.2Minecraft: 1.20.5neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.1Minecraft: 1.20.4neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 1.20.3neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 1.20.2neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.2Minecraft: 1.20.1neoforge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.7Minecraft: 26.1.2forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 26.1.1forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.1Minecraft: 26.1forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.1Minecraft: 1.21.11forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.2Minecraft: 1.21.10forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.2Minecraft: 1.21.9forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.7Minecraft: 1.21.8forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.2Minecraft: 1.21.7forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.1Minecraft: 1.21.6forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.4Minecraft: 1.21.5forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 1.21.4forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 1.21.3forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.6Minecraft: 1.21.2forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.1Minecraft: 1.21.1forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 1.21forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.4Minecraft: 1.20.6forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 1.20.5forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.4Minecraft: 1.20.4forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.2Minecraft: 1.20.3forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 1.20.2forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.34Minecraft: 1.20.1forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 1.20forge
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.65Minecraft: 26.1.2fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.4Minecraft: 26.1.1fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.10Minecraft: 26.1fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.9Minecraft: 1.21.11fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.7Minecraft: 1.21.10fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 1.21.9fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.6Minecraft: 1.21.8fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.5Minecraft: 1.21.7fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.4Minecraft: 1.21.6fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.4Minecraft: 1.21.5fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.6Minecraft: 1.21.4fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.3Minecraft: 1.21.3fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.6Minecraft: 1.21.2fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.11Minecraft: 1.21.1fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.4Minecraft: 1.21fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.5Minecraft: 1.20.6fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.5Minecraft: 1.20.5fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.7Minecraft: 1.20.4fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.4Minecraft: 1.20.3fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.6Minecraft: 1.20.2fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.7Minecraft: 1.20.1fabric, quilt
beta
Retromod 1.0.0-beta.10
1.0.0-beta.1023 мая 2026 г.5Minecraft: 1.20fabric, quilt
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.37Minecraft: 26.1.2neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.4Minecraft: 1.21.11neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.6Minecraft: 1.21.10neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.1Minecraft: 1.21.9neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.5Minecraft: 1.21.8neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.3Minecraft: 1.21.7neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.3Minecraft: 1.21.6neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.2Minecraft: 1.21.5neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.3Minecraft: 1.21.4neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.5Minecraft: 1.21.3neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.4Minecraft: 1.21.2neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.46Minecraft: 1.21.1neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.3Minecraft: 1.21neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.3Minecraft: 1.20.6neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.2Minecraft: 1.20.5neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.5Minecraft: 1.20.4neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.3Minecraft: 1.20.3neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.5Minecraft: 1.20.2neoforge
beta
Retromod 1.0.0-beta.9
1.0.0-beta.922 мая 2026 г.9Minecraft: 1.20.1neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.21Minecraft: 26.1.2neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.5Minecraft: 1.21.11neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.3Minecraft: 1.21.10neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.5Minecraft: 1.21.9neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.3Minecraft: 1.21.8neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.5Minecraft: 1.21.7neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.4Minecraft: 1.21.6neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.4Minecraft: 1.21.5neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.3Minecraft: 1.21.4neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.6Minecraft: 1.21.3neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.4Minecraft: 1.21.2neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.31Minecraft: 1.21.1neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.5Minecraft: 1.21neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.4Minecraft: 1.20.6neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.5Minecraft: 1.20.5neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.3Minecraft: 1.20.4neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.4Minecraft: 1.20.3neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.4Minecraft: 1.20.2neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.5Minecraft: 1.20.1neoforge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.9Minecraft: 26.1.2forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.2Minecraft: 26.1.1forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.3Minecraft: 26.1forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.4Minecraft: 1.21.11forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.3Minecraft: 1.21.10forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.2Minecraft: 1.21.9forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.4Minecraft: 1.21.8forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.1Minecraft: 1.21.7forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.1Minecraft: 1.21.6forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.1Minecraft: 1.21.5forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.2Minecraft: 1.21.4forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.3Minecraft: 1.21.3forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.2Minecraft: 1.21.2forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.10Minecraft: 1.21.1forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.1Minecraft: 1.21forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.2Minecraft: 1.20.6forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.1Minecraft: 1.20.5forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.1Minecraft: 1.20.4forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.1Minecraft: 1.20.3forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.4Minecraft: 1.20.2forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.46Minecraft: 1.20.1forge
beta
Retromod 1.0.0-beta.8
1.0.0-beta.822 мая 2026 г.1Minecraft: 1.20forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.11Minecraft: 26.1.2neoforge
Что нового
Fixed some bugs and added a restart popup
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.21.11neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.1Minecraft: 1.21.10neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.21.9neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.21.8neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.2Minecraft: 1.21.7neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.1Minecraft: 1.21.6neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.21.5neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.2Minecraft: 1.21.4neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.5Minecraft: 1.21.3neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.4Minecraft: 1.21.2neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.12Minecraft: 1.21.1neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.21neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.20.6neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.1Minecraft: 1.20.5neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.2Minecraft: 1.20.4neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.20.3neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.20.2neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.1Minecraft: 1.20.1neoforge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.4Minecraft: 26.1.2forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.1Minecraft: 26.1.1forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 26.1forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.21.11forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.2Minecraft: 1.21.10forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.4Minecraft: 1.21.9forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.2Minecraft: 1.21.8forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.21.7forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.1Minecraft: 1.21.6forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.2Minecraft: 1.21.5forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.2Minecraft: 1.21.4forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.1Minecraft: 1.21.3forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.21.2forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.2Minecraft: 1.21.1forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.1Minecraft: 1.21forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.20.6forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.2Minecraft: 1.20.5forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.1Minecraft: 1.20.4forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.1Minecraft: 1.20.3forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.4Minecraft: 1.20.2forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.7Minecraft: 1.20.1forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.2Minecraft: 1.20forge
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.69Minecraft: 26.1.2fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.2Minecraft: 26.1.1fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.12Minecraft: 26.1fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.27Minecraft: 1.21.11fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.5Minecraft: 1.21.10fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.2Minecraft: 1.21.9fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.5Minecraft: 1.21.8fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.2Minecraft: 1.21.7fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.21.6fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.21.5fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.21.4fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.21.3fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.21.2fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.20Minecraft: 1.21.1fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.21fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.20.6fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.4Minecraft: 1.20.5fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.20.4fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.20.3fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.20.2fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.7Minecraft: 1.20.1fabric, quilt
beta
Retromod 1.0.0-beta.7
1.0.0-beta.721 мая 2026 г.3Minecraft: 1.20fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.15Minecraft: 26.1.2neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.5Minecraft: 1.21.11neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.2Minecraft: 1.21.10neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.2Minecraft: 1.21.9neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.3Minecraft: 1.21.8neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.2Minecraft: 1.21.7neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.3Minecraft: 1.21.6neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.0Minecraft: 1.21.5neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.2Minecraft: 1.21.4neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.4Minecraft: 1.21.3neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.3Minecraft: 1.21.2neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.45Minecraft: 1.21.1neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.4Minecraft: 1.21neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.1Minecraft: 1.20.6neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.1Minecraft: 1.20.5neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.2Minecraft: 1.20.4neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.1Minecraft: 1.20.3neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.5Minecraft: 1.20.2neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.10Minecraft: 1.20.1neoforge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.9Minecraft: 26.1.2forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.2Minecraft: 26.1.1forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.8Minecraft: 26.1forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.4Minecraft: 1.21.11forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.2Minecraft: 1.21.10forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.2Minecraft: 1.21.9forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.1Minecraft: 1.21.8forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.2Minecraft: 1.21.7forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.3Minecraft: 1.21.6forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.1Minecraft: 1.21.5forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.1Minecraft: 1.21.4forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.2Minecraft: 1.21.3forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.2Minecraft: 1.21.2forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.7Minecraft: 1.21.1forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.3Minecraft: 1.21forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.3Minecraft: 1.20.6forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.4Minecraft: 1.20.5forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.1Minecraft: 1.20.4forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.2Minecraft: 1.20.3forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.3Minecraft: 1.20.2forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.28Minecraft: 1.20.1forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.4Minecraft: 1.20forge
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.21Minecraft: 26.1.2fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.5Minecraft: 26.1.1fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.4Minecraft: 26.1fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.13Minecraft: 1.21.11fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.5Minecraft: 1.21.10fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.1Minecraft: 1.21.9fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.3Minecraft: 1.21.8fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.4Minecraft: 1.21.7fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.3Minecraft: 1.21.6fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.2Minecraft: 1.21.5fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.5Minecraft: 1.21.4fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.3Minecraft: 1.21.3fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.5Minecraft: 1.21.2fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.13Minecraft: 1.21.1fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.5Minecraft: 1.21fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.5Minecraft: 1.20.6fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.3Minecraft: 1.20.5fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.2Minecraft: 1.20.4fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.4Minecraft: 1.20.3fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.3Minecraft: 1.20.2fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.6Minecraft: 1.20.1fabric, quilt
beta
Retromod 1.0.0-beta.6
1.0.0-beta.620 мая 2026 г.3Minecraft: 1.20fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.25Minecraft: 26.1.2neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.7Minecraft: 1.21.11neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.2Minecraft: 1.21.10neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.2Minecraft: 1.21.9neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.2Minecraft: 1.21.8neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.3Minecraft: 1.21.7neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.1Minecraft: 1.21.6neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.2Minecraft: 1.21.5neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.3Minecraft: 1.21.4neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.2Minecraft: 1.21.3neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.1Minecraft: 1.21.2neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.55Minecraft: 1.21.1neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.2Minecraft: 1.21neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.3Minecraft: 1.20.6neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.2Minecraft: 1.20.5neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.2Minecraft: 1.20.4neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.3Minecraft: 1.20.3neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.3Minecraft: 1.20.2neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.10Minecraft: 1.20.1neoforge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.9Minecraft: 26.1.2forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.2Minecraft: 26.1.1forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.8Minecraft: 26.1forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.1Minecraft: 1.21.11forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.3Minecraft: 1.21.10forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.2Minecraft: 1.21.9forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.2Minecraft: 1.21.8forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.520 мая 2026 г.2Minecraft: 1.21.7forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.3Minecraft: 1.21.6forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.1Minecraft: 1.21.5forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.2Minecraft: 1.21.4forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.2Minecraft: 1.21.3forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.4Minecraft: 1.21.2forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.4Minecraft: 1.21.1forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.1Minecraft: 1.21forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.4Minecraft: 1.20.6forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.5Minecraft: 1.20.5forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.2Minecraft: 1.20.4forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.3Minecraft: 1.20.3forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.2Minecraft: 1.20.2forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.43Minecraft: 1.20.1forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.3Minecraft: 1.20forge
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.38Minecraft: 26.1.2fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.5Minecraft: 26.1.1fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.10Minecraft: 26.1fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.6Minecraft: 1.21.11fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.3Minecraft: 1.21.10fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.2Minecraft: 1.21.9fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.2Minecraft: 1.21.8fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.1Minecraft: 1.21.7fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.3Minecraft: 1.21.6fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.2Minecraft: 1.21.5fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.2Minecraft: 1.21.4fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.4Minecraft: 1.21.3fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.3Minecraft: 1.21.2fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.11Minecraft: 1.21.1fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.2Minecraft: 1.21fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.3Minecraft: 1.20.6fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.2Minecraft: 1.20.5fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.1Minecraft: 1.20.4fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.2Minecraft: 1.20.3fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.2Minecraft: 1.20.2fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.7Minecraft: 1.20.1fabric, quilt
beta
Retromod 1.0.0-beta.5
1.0.0-beta.519 мая 2026 г.1Minecraft: 1.20fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.8Minecraft: 26.1.2neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.1Minecraft: 1.21.11neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.3Minecraft: 1.21.10neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.21.9neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.1Minecraft: 1.21.8neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.1Minecraft: 1.21.7neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.3Minecraft: 1.21.6neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.1Minecraft: 1.21.5neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.3Minecraft: 1.21.4neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.21.3neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.21.2neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.23Minecraft: 1.21.1neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.1Minecraft: 1.21neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.3Minecraft: 1.20.6neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.1Minecraft: 1.20.5neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.20.4neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.20.3neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.20.2neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.4Minecraft: 1.20.1neoforge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.4Minecraft: 26.1.2forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.6Minecraft: 26.1.1forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.4Minecraft: 26.1forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.1Minecraft: 1.21.11forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.21.10forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.3Minecraft: 1.21.9forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.4Minecraft: 1.21.8forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.4Minecraft: 1.21.7forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.21.6forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.3Minecraft: 1.21.5forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.21.4forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.3Minecraft: 1.21.3forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.1Minecraft: 1.21.2forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.7Minecraft: 1.21.1forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.1Minecraft: 1.21forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.20.6forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.20.5forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.20.4forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.20.3forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.3Minecraft: 1.20.2forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.10Minecraft: 1.20.1forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.4Minecraft: 1.20forge
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.10Minecraft: 26.1.2fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 26.1.1fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.6Minecraft: 26.1fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.4Minecraft: 1.21.11fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.21.10fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.21.9fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.4Minecraft: 1.21.8fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.6Minecraft: 1.21.7fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.21.6fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.21.5fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.3Minecraft: 1.21.4fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.3Minecraft: 1.21.3fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.3Minecraft: 1.21.2fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.6Minecraft: 1.21.1fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.5Minecraft: 1.21fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.4Minecraft: 1.20.6fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.4Minecraft: 1.20.5fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.20.4fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.2Minecraft: 1.20.3fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.4Minecraft: 1.20.2fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.3Minecraft: 1.20.1fabric, quilt
beta
Retromod 1.0.0-beta.4
1.0.0-beta.419 мая 2026 г.4Minecraft: 1.20fabric, quilt
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.29Minecraft: 26.1.2forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.3Minecraft: 26.1.1forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.2Minecraft: 26.1forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.5Minecraft: 1.21.11forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.2Minecraft: 1.21.10forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.3Minecraft: 1.21.9forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.2Minecraft: 1.21.8forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.2Minecraft: 1.21.7forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.3Minecraft: 1.21.6forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.2Minecraft: 1.21.5forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.3Minecraft: 1.21.4forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.2Minecraft: 1.21.3forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.2Minecraft: 1.21.2forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.11Minecraft: 1.21.1forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.1Minecraft: 1.21forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.1Minecraft: 1.20.6forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.1Minecraft: 1.20.5forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.1Minecraft: 1.20.4forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.3Minecraft: 1.20.3forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.2Minecraft: 1.20.2forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.51Minecraft: 1.20.1forge
beta
Retromod 1.0.0-beta.3
1.0.0-beta.318 мая 2026 г.4Minecraft: 1.20forge
Что нового
Hotfix for the JPMS module conflict that prevented beta.2 from loading
on Forge 1.20.x. Forge's module resolver was refusing to load Retromod
because two modules (retromod and com.google.gson) both exported the
same Gson packages to the minecraft module.
Fix: every bundled dependency is now relocated under com.retromod.shaded.*
so there's nothing to collide with Forge's own bundled libraries.
Behavior is unchanged from beta.2 otherwise. Fabric, Quilt, and NeoForge
users were not affected by this bug and don't need to update (beta.2 is
fine for them).
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.27Minecraft: 26.1.2neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.4Minecraft: 1.21.11neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.4Minecraft: 1.21.10neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.21.9neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.1Minecraft: 1.21.8neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.3Minecraft: 1.21.7neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.3Minecraft: 1.21.6neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.1Minecraft: 1.21.5neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.21.4neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.21.3neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.1Minecraft: 1.21.2neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.60Minecraft: 1.21.1neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.1Minecraft: 1.21neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.5Minecraft: 1.20.6neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.20.5neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.1Minecraft: 1.20.4neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.20.3neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.20.2neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.3Minecraft: 1.20.1neoforge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.5Minecraft: 26.1.2forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 26.1.1forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.1Minecraft: 26.1forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.1Minecraft: 1.21.11forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.1Minecraft: 1.21.10forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.3Minecraft: 1.21.9forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.21.8forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.1Minecraft: 1.21.7forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.21.6forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.21.5forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.21.4forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.21.3forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.5Minecraft: 1.21.2forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.4Minecraft: 1.21.1forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.3Minecraft: 1.21forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.20.6forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.1Minecraft: 1.20.5forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.20.4forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.5Minecraft: 1.20.3forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.20.2forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.8Minecraft: 1.20.1forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.5Minecraft: 1.20forge
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.48Minecraft: 26.1.2fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.4Minecraft: 26.1.1fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.7Minecraft: 26.1fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.11Minecraft: 1.21.11fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.21.10fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.1Minecraft: 1.21.9fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.4Minecraft: 1.21.8fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.3Minecraft: 1.21.7fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.4Minecraft: 1.21.6fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.21.5fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.5Minecraft: 1.21.4fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.4Minecraft: 1.21.3fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.4Minecraft: 1.21.2fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.19Minecraft: 1.21.1fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.7Minecraft: 1.21fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.3Minecraft: 1.20.6fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.20.5fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.5Minecraft: 1.20.4fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.2Minecraft: 1.20.3fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.3Minecraft: 1.20.2fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.11Minecraft: 1.20.1fabric, quilt
beta
Retromod 1.0.0-beta.2
1.0.0-beta.218 мая 2026 г.3Minecraft: 1.20fabric, quilt
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.21Minecraft: 26.1.2neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.4Minecraft: 1.21.11neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.3Minecraft: 1.21.10neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.2Minecraft: 1.21.9neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.4Minecraft: 1.21.8neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.3Minecraft: 1.21.7neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.2Minecraft: 1.21.6neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.1Minecraft: 1.21.5neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.2Minecraft: 1.21.4neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.2Minecraft: 1.21.3neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.2Minecraft: 1.21.2neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.30Minecraft: 1.21.1neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.1Minecraft: 1.21neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.2Minecraft: 1.20.6neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.2Minecraft: 1.20.5neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.3Minecraft: 1.20.4neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.1Minecraft: 1.20.3neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.3Minecraft: 1.20.2neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.5Minecraft: 1.20.1neoforge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.13Minecraft: 26.1.2forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.4Minecraft: 26.1.1forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.4Minecraft: 26.1forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.3Minecraft: 1.21.11forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.2Minecraft: 1.21.10forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.5Minecraft: 1.21.9forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.1Minecraft: 1.21.8forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.1Minecraft: 1.21.7forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.2Minecraft: 1.21.6forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.3Minecraft: 1.21.5forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.1Minecraft: 1.21.4forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.1Minecraft: 1.21.3forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.0Minecraft: 1.21.2forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.6Minecraft: 1.21.1forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.3Minecraft: 1.21forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.1Minecraft: 1.20.6forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.2Minecraft: 1.20.5forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.2Minecraft: 1.20.4forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.1Minecraft: 1.20.3forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.4Minecraft: 1.20.2forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.22Minecraft: 1.20.1forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.8Minecraft: 1.20forge
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.37Minecraft: 26.1.2fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.6Minecraft: 26.1.1fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.12Minecraft: 26.1fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.21Minecraft: 1.21.11fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.4Minecraft: 1.21.10fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.2Minecraft: 1.21.9fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.4Minecraft: 1.21.8fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.3Minecraft: 1.21.7fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.4Minecraft: 1.21.6fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.3Minecraft: 1.21.5fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.5Minecraft: 1.21.4fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.5Minecraft: 1.21.3fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.5Minecraft: 1.21.2fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.10Minecraft: 1.21.1fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.3Minecraft: 1.21fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.5Minecraft: 1.20.6fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.5Minecraft: 1.20.5fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.4Minecraft: 1.20.4fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.6Minecraft: 1.20.3fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.4Minecraft: 1.20.2fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.9Minecraft: 1.20.1fabric
beta
Retromod 1.0.0-beta.1
1.0.0-beta.112 мая 2026 г.9Minecraft: 1.20fabric